npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@firebase-graphql/graphql-codegen-firestore-rules

v0.0.10

Published

<div align="center">

Downloads

20

Readme

@firebase-graphql/graphql-codegen-firestore-rules

npm downloads npm npm

Abstract

  • This package is a member of the firebase-graphql package family.
  • This package generates a Firestore Rules file from a GraphQL schema which defined firestore structure.
  • You can use this package without GraphQL

Example

First, you need to define the structure of the firestore according to the GraphQL format.

# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [
      { allow: owner, ownerField: "id", operations: [create, update] }
      { allow: public, operations: [get, list] }
    ]
  ) {
  id: ID!
  name: String!
  age: Int
}

type Post
  @firestore(document: "/users/{userId}/posts/{id}")
  @auth(
    rules: [
      {
        allow: owner
        ownerField: "userId"
        operations: [create, update, delete]
      }
      { allow: public, operations: [get, list] }
    ]
  ) {
  id: ID!
  userId: ID!
  title: String!
  content: String!
}

Then, you can generate the Firestore Rules file. Like this:

rules_version = "2"
service cloud.firestore {
  match /databases/{database}/documents {
    function isString(value) {
      return value is string
    }
    function isInt(value) {
      return value is int
    }
    function isBoolean(value) {
      return value is bool
    }
    function isFloat(value) {
      return value is float
    }
    function isID(value) {
      return value is string
    }
    function isDate(value) {
      return value is timestamp
    }
    function isMap(value) {
      return value is map
    }
    function isRequired(source, field) {
      return field in source && source[field] != null
    }
    function isNullable(source, field) {
      return !(field in source) || source[field] == null
    }
    function isLoggedIn() {
      return request.auth != null
    }
    function isAuthUserId(userId) {
      return isLoggedIn() && request.auth.uid == userId
    }
    function isPost(value) {
      return (
        isMap(value) && value.keys().hasOnly(["__typename", "content", "title"])
        && isRequired(value, "__typename") && isString(value.__typename) && value.__typename == "Post"
        && isRequired(value, "content") && isString(value.content)
        && isRequired(value, "title") && isString(value.title)
      )
    }
    function isUser(value) {
      return (
        isMap(value) && value.keys().hasOnly(["__typename", "age", "name"])
        && isRequired(value, "__typename") && isString(value.__typename) && value.__typename == "User"
        && (isNullable(value, "age") || isInt(value.age))
        && isRequired(value, "name") && isString(value.name)
      )
    }
    match /users/{userId}/posts/{id} {
      allow get: if (
        true
      )
      allow list: if (
        true
      )
      allow create: if (
        isPost(request.resource.data)
        && isAuthUserId(userId)
      )
      allow update: if (
        isPost(request.resource.data)
        && isAuthUserId(userId)
      )
      allow delete: if (
        isAuthUserId(userId)
      )
    }
    match /users/{id} {
      allow get: if (
        true
      )
      allow list: if (
        true
      )
      allow create: if (
        isUser(request.resource.data)
        && isAuthUserId(id)
      )
      allow update: if (
        isUser(request.resource.data)
        && isAuthUserId(id)
      )
    }
  }
}

How to use

Install

yarn add -D @firebase-graphql/graphql-codegen-firestore-rules @graphql-codegen/cli graphql

Make schema file

# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [
      { allow: owner, ownerField: "id", operations: [create, update] }
      { allow: public, operations: [get, list] }
    ]
  ) {
  id: ID!
  name: String!
  age: Int
}

Configure graphql-codegen file

# codegen.yml
generates:
  firestore.rules:
    schema: firestore.graphql
    plugins:
      - '@firebase-graphql/graphql-codegen-firestore-rules'

Generate Firestore Rules file

yarn graphql-codegen

Concept and Usage

Data Validation

this package generate validation functions for firestore.rules.

Literals

This package supports the following literals:

  • String → tha value should be a string
  • Int → tha value should be a int
  • Float → tha value should be a float
  • Boolean → tha value should be a bool
  • ID → tha value should be a string
  • Date → tha value should be a timestamp

Enums

You can define enums in the schema.

# firestore.graphql

enum Category {
  IT
  MARKETING
  EDUCATION
}

Then, this package generates validation functions for enums. like this:

# firestore.rules
function isCategory(value) {
  return value is string && value in ["IT", "MARKETING", "EDUCATION"]
}

Other Types

# firestore.graphql

type Image {
  url: String!
  width: Int!
  height: Int!
}

type User {
  id: ID!
  name: String!
  age: Int
  profileImage: Image
}

type Post {
  id: ID!
  title: String!
  content: String!
  header: Image!
  author: User!
}

Then,

# firestore.rules
function isImage(value) {
  return (
    isMap(value) && value.keys().hasOnly(["url", "width", "height"])
    && isRequired(value, "url") && isString(value.url)
    && isRequired(value, "width") && isInt(value.width)
    && isRequired(value, "height") && isInt(value.height)
  )
}
functions isUser(value) {
  return (
    isMap(value) && value.keys().hasOnly(["id", "name", "age", "profileImage"])
    && isRequired(value, "id") && isID(value.id)
    && isRequired(value, "name") && isString(value.name)
    && (isNullable(value, "age") || isInt(value.age))
    && isRequired(value, "profileImage") && isImage(value.profileImage)
  )
}
function isPost(value) {
  return (
    isMap(value) && value.keys().hasOnly(["id", "title", "content", "header", "author"])
    && isRequired(value, "id") && isID(value.id)
    && isRequired(value, "title") && isString(value.title)
    && isRequired(value, "content") && isString(value.content)
    && isRequired(value, "header") && isImage(value.header)
    && isRequired(value, "author") && isUser(value.author)
  )
}

Firestore Document Access

You can define Type with @firestore directive.

# firestore.graphql
type User @firestore(document: "/users/{id}") {
  id: ID!
  name: String!
  age: Int
}

By doing this, you can generate a match expression for firestore.rules

# firestore.rules
match /users/{userId} {
}

This way the id field of the User will be treated as a special field and automatically understood to use the value from the path /users/{id}.

So, the validation for User will be as follows.

# firestore.rules

# Id is not included in the validation, since it is a special field obtained from the path.
function isUser(value) {
  return (
    isMap(value) && value.keys().hasOnly(["__typename", "age", "name"])
    && isRequired(value, "__typename") && isString(value.__typename) && value.__typename == "User"
    && (isNullable(value, "age") || isInt(value.age))
    && isRequired(value, "name") && isString(value.name)
  )
}

It is recommended that the @firestore directive be used in conjunction with the @auth directive, described next.

Access Control

The @firestore can be followed by an @auth directive to define access controls for that data.

Like this,

# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(rules: [{ allow: public, operations: [get, list] }]) {
  id: ID!
  name: String!
  age: Int
}

The @auth directive needs to be specified with a rules argument. These rules are calculated as a disjunction.

The format of the rule is as follows

| Field | Type | required | Description | | ---------- | ---------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------- | | allow | public, private, owner | :white_check_mark: | type of access control | | operations | list of {get, list,create, update, delete} | :white_check_mark: | The operations to allow access to. | | ownerField | string (but should be field name) | :negative_squared_cross_mark: (if allow: owner then :white_check_mark:) | Compare that field with the ID of the user accessing it. |

Examples

Anyone can read and write.
# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [{ allow: public, operations: [get, list, create, update, delete] }]
  ) {
  id: ID!
  name: String!
  age: Int
}
# firestore.rules

match /users/{id} {
  allow get: if (
    true
  )
  allow list: if (
    true
  )
  allow create: if (
    isUser(request.resource.data)
    && true
  )
  allow update: if (
    isUser(request.resource.data)
    && true
  )
  allow delete: if (
    true
  )
}
Anyone who is logged in can read and write.
# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [{ allow: private, operations: [get, list, create, update, delete] }]
  ) {
  id: ID!
  name: String!
  age: Int
}
# firestore.rules

match /users/{id} {
  allow get: if (
    isLoggedIn()
  )
  allow list: if (
    isLoggedIn()
  )
  allow create: if (
    isUser(request.resource.data)
    && isLoggedIn()
  )
  allow update: if (
    isUser(request.resource.data)
    && isLoggedIn()
  )
  allow delete: if (
    isLoggedIn()
  )
}
Only the owner can read and write.
# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [
      {
        allow: owner
        operations: [get, list, create, update, delete]
        ownerField: "id"
      }
    ]
  ) {
  id: ID!
  name: String!
  age: Int
}
# firestore.rules

match /users/{id} {
  allow get: if (
    isAuthUserId(id)
  )
  allow list: if (
    isAuthUserId(id)
  )
  allow create: if (
    isUser(request.resource.data)
    && isAuthUserId(id)
  )
  allow update: if (
    isUser(request.resource.data)
    && isAuthUserId(id)
  )
  allow delete: if (
    isAuthUserId(id)
  )
}

If the ownerField is not path-based

# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [
      {
        allow: owner
        operations: [get, list, create, update, delete]
        ownerField: "userId"
      }
    ]
  ) {
  id: ID!
  userId: ID!
  name: String!
  age: Int
}
# firestore.rules

match /users/{id} {
  allow get: if (
    isAuthUserId(resource.data.userId)
  )
  allow list: if (
    isAuthUserId(resource.data.userId)
  )
  allow create: if (
    isUser(request.resource.data)
    && isAuthUserId(request.resource.data.userId)
  )
  allow update: if (
    isUser(request.resource.data)
    && (isAuthUserId(request.resource.data.userId) && isAuthUserId(resource.data.userId))
  )
  allow delete: if (
    isAuthUserId(resource.data.userId)
  )
}

ServerTimestamp

You may want to match the value of a field with its creation or update time, such as createdAt or updatedAt

In such cases, the @createdAt and @updatedAt directives can be used.

# firestore.graphql
type User
  @firestore(document: "/users/{id}")
  @auth(
    rules: [{ allow: private, operations: [get, list, create, update, delete] }]
  ) {
  id: ID!
  name: String!
  age: Int
  # The field name can be anything other than "createdAt" or "updatedAt".
  createdAt: Date! @createdAt
  updatedAt: Date! @updatedAt
}

And this will generate the following rules

# firestore.rules

match /users/{id} {
  allow get: if (
    isLoggedIn()
  )
  allow list: if (
    isLoggedIn()
  )
  allow create: if (
    isUser(request.resource.data)
    && request.resource.data.createdAt == request.time
    && request.resource.data.updatedAt == request.time
    && isLoggedIn()
  )
  allow update: if (
    isUser(request.resource.data)
    && !("createdAt" in request.resource.data)
    && request.resource.data.updatedAt == request.time
    && isLoggedIn()
  )
  allow delete: if (
    isLoggedIn()
  )
}

Contributors

License

License: MIT