firebase_rules 0.0.1+1 copy "firebase_rules: ^0.0.1+1" to clipboard
firebase_rules: ^0.0.1+1 copied to clipboard

A type-safe Firebase rules generator for Firestore, Storage, and Realtime Database

A type-safe Firebase rules generator for Firestore, Storage, and Realtime Database

Features #

  • Create rules for Firestore, Storage, and Realtime Database in a type-safe environment
  • Mimics the Firebase rules syntax for easy migration

Limitations #

  • All rules functions must be defined at the root level
  • Realtime Database rules are not really type-safe, but you do get the benefit of having code completion

Installation #

dependencies:
  firebase_rules: latest
  # If you are using types from `cloud_firestore_platform_interface`
  firebase_rules_convert: latest

dev_dependencies:
    build_runner: latest
    firebase_rules_generator: latest

Usage #

Annotations #

The starting point of all rules is the annotation. Firestore, Storage, and Database rules should be defined in their own files to prevent conflicts.

import 'package:firebase_rules/database.dart';
import 'package:firebase_rules/firebase.dart';

/// Create rules for Firestore
@FirebaseRules(service: Service.firestore)
final firestoreRules = [];

/// Create rules for Storage
@FirebaseRules(service: Service.storage)
final storageRules = [];

/// Create rules for Realtime Database
@DatabaseRules()
final databaseRules = [];

Paths #

Patch classes allow for type-safe access to path parameters

import 'package:firebase_rules/firebase.dart';

/// Path classes should be `abstract` and extend [FirebasePath]. Since this
/// code isn't actually going to run, implementations are unnecessary.
abstract class UsersPath extends FirebasePath {
  /// Path parameters should be strings
  String get userId;

  /// Create a path using literals and interpolate the path parameters. The
  /// generator will convert this to a firebase path.
  @override
  String get path => '/users/$userId';
}

abstract class User {
  String get userId;
}

Matches #

Now we can start defining Match statements

import 'package:firebase_rules/firebase.dart';
import 'paths.dart';

@FirebaseRules(service: Service.firestore)
final firestoreRules = [
  /// Always start with this match. [FirestorePath] is the root of Firestore.
  Match<FirestorePath, FirestoreResource>(
    /// Match statements give access to type-safe contextual information:
    /// - [path] is the path class of this match
    /// - [request] gives access to the [Request] object
    /// - [resource] gives access to the [Resource] object
    ///
    /// The [path] parameter can have any name, but [request] and [resource]
    /// must not  be renamed
    matches: (path, request, resource) => [
      /// Subsequent matches should use typed [FirestoreResource] objects.
      /// This makes the [request] and [resource] parameters type-safe.
      Match<UsersPath, FirestoreResource<User>>(),
    ],
  ),
];

@FirebaseRules(service: Service.storage)
final storageRules = [
  /// Always start with this match. [StoragePath] is the root of Storage.
  Match<StoragePath, StorageResource>(
    matches: (path, request, resource) => [
      /// All storage matches use [StorageResource] objects
      Match<UsersPath, StorageResource>(),
    ],
  ),
];

Rules #

Rules are why we're here

import 'package:firebase_rules/firebase.dart';
import 'paths.dart';

@FirebaseRules(service: Service.firestore)
final firestoreRules = [
  Match<FirestorePath, FirestoreResource>(
    matches: (path, request, resource) => [
      Match<UsersPath, FirestoreResource<User>>(
        rules: (usersPath, request, resource) => [
          Allow([Operation.read], resource.data.userId == usersPath.userId)
        ],
      ),
    ],
  ),
];

Rules Language #

This package contains a reimplementation of the Firebase rules language in Dart. These calls are translated to the correct Firebase rules syntax by the generator.

import 'package:firebase_rules/firebase.dart';

void example() {
  /// Dart objects can be converted to rules objects by calling `rules` on them
  ''.rules.range(0, 1);

  /// Methods called on `rules` types also take `rules` types as arguments
  [].rules.concat([].rules);

  /// Types from `cloud_firestore_platform_interface` can also be converted
  /// with the `firebase_rules_convert` package
  /// ex: `Blob`, `GeoPoint`, `Timestamp`
}

Functions #

Top-level functions can be used as functions rules

import 'package:firebase_rules/firebase.dart';

/// All functions must return a bool
bool isSignedIn(RulesRequest request) {
  /// Null-safety operators will be stripped by the generator
  return request.auth?.uid != null;
}

@FirebaseRules(
  service: Service.firestore,

  /// Functions must be declared here in order to be generated. This allows for
  /// one project to contain multiple rulesets.
  functions: [isSignedIn],
)
final rules = [];

Organization #

Any of the function arguments of a match statement can be split out for organization

import 'package:firebase_rules/firebase.dart';
import 'paths.dart';

/// Match parameter functions can be split out for organization. These functions
/// can be in any file in the project. Note that match functions cannot contain
/// a body.
List<Match> detached(path, request, resource) => [
      Match<UsersPath, FirestoreResource<User>>(
        rules: (usersPath, request, resource) => [
          Allow([Operation.read], resource.data.userId == usersPath.userId)
        ],
      ),
    ];

@FirebaseRules(service: Service.firestore)
final firestoreRules = [
  Match<FirestorePath, FirestoreResource>(matches: detached),
];

Generation #

Finally, we can run the generator

$ dart pub run build_runner build

For every rules.dart file, this will generate a rules.rules file in the same directory. Point your Firebase config to this file to use the rules.

Realtime Database #

Database rules are similar to Firestore and Storage rules, but they have a few differences:

  • Database paths are raw strings
  • The first match must start with rules. That is the root of the database.
  • Wildcards are denoted with $
  • If a path has a wildcard, it must be the last path segment. Database paths can only contain at most one wildcard.
import 'package:firebase_rules/database.dart';

@DatabaseRules()
final databaseRules = [
  Match(
    r'rules/users/$userId',
    read: (userId) => auth != null && auth?.uid == userId,
    write: (userId) => userId == 'user1'.rules,
    validate: (userId) => !data.exists(),
    indexOn: ['uid', 'email'],
    matches: (userId) => [
      Match(
        r'contracts/$contractId',
        read: (contractId) =>
            root.child('users'.rules).child(userId).child(contractId).val() !=
            null,
        write: (contractId) =>
            root.child('users'.rules).child(userId).child(contractId).val() !=
            null,
      ),
    ],
  ),
];

Additional Information #

This package is still early in development. If you encounter any issues, please create an issue with a minimal reproducible sample.

14
likes
0
pub points
60%
popularity

Publisher

verified publisheriodesignteam.com

A type-safe Firebase rules generator for Firestore, Storage, and Realtime Database

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

meta

More

Packages that depend on firebase_rules