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 🙏

© 2026 – Pkg Stats / Ryan Hefner

auth-cloud-sdk

v0.0.1

Published

Client-side SDK for interacting with authentication and authorization cloud functions.

Readme

Auth Cloud SDK Documentation

Table of Contents

  1. Introduction
  2. Getting Started
  3. Core Concepts
  4. API Reference
  5. Type Definitions
  6. Usage Examples
  7. Error Handling
  8. Best Practices
  9. Troubleshooting
  10. FAQ

Introduction

The Auth Cloud SDK is a client-side TypeScript/JavaScript library designed to simplify interactions with your custom authentication, authorization, and Firestore-related Google Cloud Functions. It provides a secure and type-safe way to perform authorized Firestore operations, manage authorization relationships, and handle permissions in your web applications.

Key Features

  • Authorized Firestore Operations: Execute Firestore operations with built-in authorization checks
  • SpiceDB Integration: Manage authorization relationships using SpiceDB
  • Type Safety: Full TypeScript support with comprehensive type definitions
  • Firebase Authentication: Seamless integration with Firebase Authentication
  • Error Handling: Consistent error handling across all operations

Architecture Overview

The Auth Cloud SDK acts as a bridge between your client application and your backend Google Cloud Functions. It leverages:

  • Firebase Authentication: For user identity and secure token generation
  • SpiceDB: For fine-grained authorization and permission management
  • Firestore: For data storage and retrieval
  • Google Cloud Functions: For secure server-side operations
┌─────────────────┐      ┌───────────────┐      ┌─────────────────────┐
│                 │      │               │      │                     │
│  Client App     │──────▶  Auth Cloud   │──────▶  Google Cloud       │
│  (React, Vue,   │      │  SDK          │      │  Functions          │
│   Angular, etc) │◀─────│               │◀─────│                     │
│                 │      │               │      │                     │
└─────────────────┘      └───────────────┘      └─────────────────────┘
                                                          │
                                                          │
                                                          ▼
                                               ┌─────────────────────┐
                                               │                     │
                                               │  Firebase Auth      │
                                               │  Firestore          │
                                               │  SpiceDB            │
                                               │                     │
                                               └─────────────────────┘

Getting Started

Installation

Install the Auth Cloud SDK using npm or yarn:

npm install auth-cloud-sdk
# or
yarn add auth-cloud-sdk

Prerequisites

Before using the Auth Cloud SDK, ensure you have:

  1. Firebase Initialized: Your client application must initialize Firebase, particularly Firebase Authentication.
// Example Firebase initialization
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
  1. Deployed Cloud Functions: The following Google Cloud Functions must be deployed:

    • executeAuthorizedFirestoreOperation
    • manageSpiceDBRelationship
    • writeSpiceDBSchema
    • lookupAuthorizedResources
  2. SpiceDB Instance: A running SpiceDB instance configured with your authorization schema.

Configuration

Before using any SDK functions, configure it with your Cloud Function endpoints:

import { setCloudFunctionEndpoints } from 'auth-cloud-sdk';

setCloudFunctionEndpoints({
  executeAuthorizedFirestoreOperation: 'https://your-region-your-project.cloudfunctions.net/executeAuthorizedFirestoreOperation',
  manageSpiceDBRelationship: 'https://your-region-your-project.cloudfunctions.net/manageSpiceDBRelationship',
  writeSpiceDBSchema: 'https://your-region-your-project.cloudfunctions.net/writeSpiceDBSchema',
  lookupAuthorizedResources: 'https://your-region-your-project.cloudfunctions.net/lookupAuthorizedResources'
});

Quick Start Example

Here's a simple example of reading a document with authorization checks:

import { executeAuthorizedFirestoreOperation, Types } from 'auth-cloud-sdk';

async function getSecureDocument(documentId) {
  try {
    // Define authorization parameters
    const authorization: Types.Authorization = {
      resourceObjectType: 'document',
      resourceObjectId: documentId,
      permission: 'read'
    };
    
    // Define Firestore operation
    const firestoreOperation: Types.FirestoreOperation = {
      operationType: 'GET_DOCUMENT',
      collectionPath: 'secureDocuments',
      documentId: documentId
    };
    
    // Execute the operation with authorization check
    const document = await executeAuthorizedFirestoreOperation(
      authorization, 
      firestoreOperation
    );
    
    return document;
  } catch (error) {
    console.error('Error fetching document:', error);
    throw error;
  }
}

Core Concepts

Authentication Model

The Auth Cloud SDK relies on Firebase Authentication for user identity. When you call any SDK function, it automatically:

  1. Retrieves the current authenticated user from Firebase Auth
  2. Gets a fresh ID token for that user
  3. Sends this token to the Cloud Function for verification

This ensures that all operations are performed in the context of an authenticated user. If no user is signed in, the SDK will throw an error.

Authorization with SpiceDB

The SDK uses SpiceDB (formerly Authzed) as its authorization system. SpiceDB implements a relationship-based authorization model where:

  • Resources are objects that users can access (documents, collections, features)
  • Relations define how subjects relate to resources (viewer, editor, owner)
  • Subjects are typically users, but can also be groups or other entities
  • Permissions are derived from relations through a schema

The SDK provides functions to:

  • Create, update, or delete relationships
  • Check if a user has permission on a resource
  • Find all resources a user has permission to access
  • Update the authorization schema

Firestore Operations with Authorization

The SDK combines Firestore operations with authorization checks in a single call. This ensures that users can only perform operations they're authorized for. Supported operations include:

  • Reading documents
  • Querying collections
  • Creating documents
  • Updating documents
  • Deleting documents

Each operation is checked against the user's permissions before execution.

Relationship Management

The SDK allows you to manage authorization relationships in SpiceDB. This includes:

  • Creating new relationships (granting permissions)
  • Touching relationships (updating timestamps)
  • Deleting relationships (revoking permissions)

API Reference

Configuration Functions

setCloudFunctionEndpoints(endpoints: CloudFunctionEndpoints): void

Configures the SDK with the necessary Cloud Function endpoint URLs. This must be called once before using any SDK functions.

Parameters:

  • endpoints: An object containing the URLs for the Cloud Functions.
    • executeAuthorizedFirestoreOperation: URL for the Firestore operations function
    • manageSpiceDBRelationship: URL for the relationship management function
    • writeSpiceDBSchema: URL for the schema writing function
    • lookupAuthorizedResources: URL for the resource lookup function

Throws:

  • Error if endpoints configuration is empty
  • Error if any required endpoint is missing

Example:

import { setCloudFunctionEndpoints } from 'auth-cloud-sdk';

setCloudFunctionEndpoints({
  executeAuthorizedFirestoreOperation: 'https://us-central1-myproject.cloudfunctions.net/executeAuthorizedFirestoreOperation',
  manageSpiceDBRelationship: 'https://us-central1-myproject.cloudfunctions.net/manageSpiceDBRelationship',
  writeSpiceDBSchema: 'https://us-central1-myproject.cloudfunctions.net/writeSpiceDBSchema',
  lookupAuthorizedResources: 'https://us-central1-myproject.cloudfunctions.net/lookupAuthorizedResources'
});

getCloudFunctionEndpoints(): CloudFunctionEndpoints

Retrieves the configured Cloud Function endpoints.

Returns:

  • An object containing the configured endpoint URLs

Throws:

  • Error if endpoints have not been set

Example:

import { getCloudFunctionEndpoints } from 'auth-cloud-sdk';

try {
  const endpoints = getCloudFunctionEndpoints();
  console.log('Configured endpoints:', endpoints);
} catch (error) {
  console.error('Endpoints not configured:', error);
}

Firestore Operations

executeAuthorizedFirestoreOperation(authorization: Authorization, firestoreOperation: FirestoreOperation): Promise<any>

Executes a Firestore operation through the designated Cloud Function, with authorization checks.

Parameters:

  • authorization: Authorization details for the operation
    • resourceObjectType: Type of resource being accessed
    • resourceObjectId: ID of the resource being accessed
    • permission: Permission required for the operation
    • subjectObjectType: (Optional) Type of the subject, defaults to 'user'
    • zedToken: (Optional) ZedToken for consistency
  • firestoreOperation: The Firestore operation to perform
    • operationType: Type of operation ('GET_DOCUMENT', 'GET_COLLECTION', 'CREATE_DOCUMENT', 'UPDATE_DOCUMENT', 'DELETE_DOCUMENT')
    • collectionPath: Path to the Firestore collection
    • documentId: (Optional) ID of the document
    • data: (Optional) Data for create/update operations
    • queryConstraints: (Optional) Query constraints for collection queries

Returns:

  • A promise that resolves with the result of the Firestore operation

Throws:

  • Error if the user is not authenticated
  • Error if the Cloud Function call fails
  • Error if the user lacks the required permission

Example:

import { executeAuthorizedFirestoreOperation, Types } from 'auth-cloud-sdk';

// Read a document
async function readDocument(docId) {
  const authorization: Types.Authorization = {
    resourceObjectType: 'document',
    resourceObjectId: docId,
    permission: 'read'
  };
  
  const operation: Types.FirestoreOperation = {
    operationType: 'GET_DOCUMENT',
    collectionPath: 'documents',
    documentId: docId
  };
  
  return executeAuthorizedFirestoreOperation(authorization, operation);
}

// Query a collection
async function queryDocuments() {
  const authorization: Types.Authorization = {
    resourceObjectType: 'collection',
    resourceObjectId: 'documents',
    permission: 'list'
  };
  
  const operation: Types.FirestoreOperation = {
    operationType: 'GET_COLLECTION',
    collectionPath: 'documents',
    queryConstraints: [
      { field: 'status', op: '==', value: 'published' },
      { orderBy: 'createdAt', direction: 'desc' },
      { limit: 10 }
    ]
  };
  
  return executeAuthorizedFirestoreOperation(authorization, operation);
}

// Create a document
async function createDocument(data) {
  const authorization: Types.Authorization = {
    resourceObjectType: 'collection',
    resourceObjectId: 'documents',
    permission: 'create'
  };
  
  const operation: Types.FirestoreOperation = {
    operationType: 'CREATE_DOCUMENT',
    collectionPath: 'documents',
    data: data
  };
  
  return executeAuthorizedFirestoreOperation(authorization, operation);
}

// Update a document
async function updateDocument(docId, data) {
  const authorization: Types.Authorization = {
    resourceObjectType: 'document',
    resourceObjectId: docId,
    permission: 'update'
  };
  
  const operation: Types.FirestoreOperation = {
    operationType: 'UPDATE_DOCUMENT',
    collectionPath: 'documents',
    documentId: docId,
    data: data
  };
  
  return executeAuthorizedFirestoreOperation(authorization, operation);
}

// Delete a document
async function deleteDocument(docId) {
  const authorization: Types.Authorization = {
    resourceObjectType: 'document',
    resourceObjectId: docId,
    permission: 'delete'
  };
  
  const operation: Types.FirestoreOperation = {
    operationType: 'DELETE_DOCUMENT',
    collectionPath: 'documents',
    documentId: docId
  };
  
  return executeAuthorizedFirestoreOperation(authorization, operation);
}

SpiceDB Relationship Management

manageSpiceDBRelationship(payload: ManageRelationshipPayload): Promise<ManageRelationshipResponse>

Manages SpiceDB relationships (create, touch, delete) through the Cloud Function.

Parameters:

  • payload: The payload containing the operation type and relationships
    • operation: Type of operation ('CREATE', 'TOUCH', 'DELETE')
    • relationships: Array of relationship objects
      • resource: The resource object (type and ID)
      • relation: The relation name
      • subject: The subject object (type and ID)

Returns:

  • A promise that resolves with the result of the operation
    • status: Operation status ('success')
    • operation: The operation that was performed
    • zedToken: ZedToken for consistency

Throws:

  • Error if the user is not authenticated
  • Error if the Cloud Function call fails

Example:

import { manageSpiceDBRelationship, Types } from 'auth-cloud-sdk';

// Grant a user viewer access to a document
async function grantViewAccess(userId, documentId) {
  const payload: Types.ManageRelationshipPayload = {
    operation: 'CREATE',
    relationships: [
      {
        resource: {
          objectType: 'document',
          objectId: documentId
        },
        relation: 'viewer',
        subject: {
          object: {
            objectType: 'user',
            objectId: userId
          }
        }
      }
    ]
  };
  
  return manageSpiceDBRelationship(payload);
}

// Grant a user editor access to a document
async function grantEditAccess(userId, documentId) {
  const payload: Types.ManageRelationshipPayload = {
    operation: 'CREATE',
    relationships: [
      {
        resource: {
          objectType: 'document',
          objectId: documentId
        },
        relation: 'editor',
        subject: {
          object: {
            objectType: 'user',
            objectId: userId
          }
        }
      }
    ]
  };
  
  return manageSpiceDBRelationship(payload);
}

// Revoke a user's access to a document
async function revokeAccess(userId, documentId, relation) {
  const payload: Types.ManageRelationshipPayload = {
    operation: 'DELETE',
    relationships: [
      {
        resource: {
          objectType: 'document',
          objectId: documentId
        },
        relation: relation, // 'viewer', 'editor', etc.
        subject: {
          object: {
            objectType: 'user',
            objectId: userId
          }
        }
      }
    ]
  };
  
  return manageSpiceDBRelationship(payload);
}

writeSpiceDBSchema(schemaText: string): Promise<WriteSchemaResponse>

Writes a new schema to SpiceDB through the Cloud Function. This operation is typically restricted to super admins.

Parameters:

  • schemaText: The SpiceDB schema definition text

Returns:

  • A promise that resolves with the result of the operation
    • status: Operation status ('success')
    • message: Success message

Throws:

  • Error if the user is not authenticated
  • Error if the Cloud Function call fails
  • Error if the user is not authorized to write schemas

Example:

import { writeSpiceDBSchema } from 'auth-cloud-sdk';

// Update the authorization schema
async function updateAuthSchema() {
  const schemaText = `
    definition user {}
    
    definition document {
      relation viewer: user
      relation editor: user
      relation owner: user
      
      permission view = viewer + editor + owner
      permission edit = editor + owner
      permission delete = owner
    }
    
    definition collection {
      relation admin: user
      
      permission create = admin
      permission list = admin
    }
  `;
  
  return writeSpiceDBSchema(schemaText);
}

lookupAuthorizedResources(resourceObjectType: string, permission: string, subjectObjectType?: string, zedToken?: string): Promise<LookupAuthorizedResourcesResponse>

Looks up resources a subject has a given permission on, via the Cloud Function.

Parameters:

  • resourceObjectType: The type of resource to look up (e.g., "document", "folder")
  • permission: The permission to check (e.g., "view", "edit")
  • subjectObjectType: (Optional) The type of the subject (e.g., "user"). Defaults to "user" if undefined
  • zedToken: (Optional) ZedToken for consistency

Returns:

  • A promise that resolves with a list of resource IDs
    • resourceIds: Array of resource IDs the user has permission on

Throws:

  • Error if the user is not authenticated
  • Error if the Cloud Function call fails

Example:

import { lookupAuthorizedResources } from 'auth-cloud-sdk';

// Find all documents the user can view
async function findViewableDocuments() {
  const result = await lookupAuthorizedResources('document', 'view');
  return result.resourceIds;
}

// Find all documents the user can edit
async function findEditableDocuments() {
  const result = await lookupAuthorizedResources('document', 'edit');
  return result.resourceIds;
}

// Find all collections the user can create documents in
async function findWritableCollections() {
  const result = await lookupAuthorizedResources('collection', 'create');
  return result.resourceIds;
}

Type Definitions

The SDK exports the following TypeScript interfaces:

CloudFunctionEndpoints

Defines the structure for the Cloud Function endpoints configuration.

interface CloudFunctionEndpoints {
  executeAuthorizedFirestoreOperation: string;
  manageSpiceDBRelationship: string;
  writeSpiceDBSchema: string;
  lookupAuthorizedResources: string;
}

FirestoreOperation

Defines the structure for a Firestore operation.

interface FirestoreOperation {
  operationType: 'GET_DOCUMENT' | 'GET_COLLECTION' | 'CREATE_DOCUMENT' | 'UPDATE_DOCUMENT' | 'DELETE_DOCUMENT';
  collectionPath: string;
  documentId?: string;
  data?: any;
  queryConstraints?: Array<{
    field?: string;
    op?: WhereFilterOp;
    value?: any;
    orderBy?: string;
    direction?: 'asc' | 'desc';
    limit?: number;
  }>;
}

Authorization

Defines the authorization details required for an operation.

interface Authorization {
  subjectObjectType?: string; // Defaults to 'user' in the Cloud Function if not provided
  resourceObjectType: string;
  resourceObjectId: string;
  permission: string;
  zedToken?: string; // Optional ZedToken for consistency
}

Relationship

Defines the structure for a single relationship to be managed in SpiceDB.

interface Relationship {
  resource: {
    objectType: string;
    objectId: string;
  };
  relation: string;
  subject: {
    object: {
      objectType?: string; // Defaults to 'user' in the Cloud Function if not provided
      objectId: string;
    };
    optionalRelation?: string;
  };
}

ManageRelationshipPayload

Defines the payload for managing SpiceDB relationships.

interface ManageRelationshipPayload {
  operation: 'CREATE' | 'TOUCH' | 'DELETE';
  relationships: Relationship[];
}

ManageRelationshipResponse

Defines the response structure for a successful relationship management operation.

interface ManageRelationshipResponse {
  status: 'success';
  operation: 'CREATE' | 'TOUCH' | 'DELETE';
  zedToken: string;
}

WriteSchemaResponse

Defines the response structure for a successful schema write operation.

interface WriteSchemaResponse {
  status: 'success';
  message: string;
}

LookupAuthorizedResourcesResponse

Defines the response structure for looking up authorized resources.

interface LookupAuthorizedResourcesResponse {
  resourceIds: string[];
}

CloudFunctionErrorResponse

Generic error response structure from Cloud Functions.

interface CloudFunctionErrorResponse {
  error: string;
}

Usage Examples

Building a Secure Document Management System

import { 
  executeAuthorizedFirestoreOperation, 
  manageSpiceDBRelationship, 
  lookupAuthorizedResources,
  Types 
} from 'auth-cloud-sdk';

class SecureDocumentManager {
  // Create a new document with the current user as owner
  async createDocument(title, content) {
    const currentUser = firebase.auth().currentUser;
    if (!currentUser) throw new Error('User not authenticated');
    
    // First, create the document
    const authorization: Types.Authorization = {
      resourceObjectType: 'collection',
      resourceObjectId: 'documents',
      permission: 'create'
    };
    
    const createOperation: Types.FirestoreOperation = {
      operationType: 'CREATE_DOCUMENT',
      collectionPath: 'documents',
      data: {
        title,
        content,
        createdBy: currentUser.uid,
        createdAt: new Date().toISOString()
      }
    };
    
    const newDoc = await executeAuthorizedFirestoreOperation(authorization, createOperation);
    
    // Then, set the current user as the owner
    const relationshipPayload: Types.ManageRelationshipPayload = {
      operation: 'CREATE',
      relationships: [
        {
          resource: {
            objectType: 'document',
            objectId: newDoc.id
          },
          relation: 'owner',
          subject: {
            object: {
              objectType: 'user',
              objectId: currentUser.uid
            }
          }
        }
      ]
    };
    
    await manageSpiceDBRelationship(relationshipPayload);
    
    return newDoc;
  }
  
  // Get all documents the user can view
  async getViewableDocuments() {
    // First, get all document IDs the user can view
    const lookupResult = await lookupAuthorizedResources('document', 'view');
    const documentIds = lookupResult.resourceIds;
    
    if (documentIds.length === 0) {
      return [];
    }
    
    // Then, fetch the documents in batches
    const documents = [];
    const batchSize = 10;
    
    for (let i = 0; i < documentIds.length; i += batchSize) {
      const batch = documentIds.slice(i, i + batchSize);
      
      for (const docId of batch) {
        const authorization: Types.Authorization = {
          resourceObjectType: 'document',
          resourceObjectId: docId,
          permission: 'view'
        };
        
        const operation: Types.FirestoreOperation = {
          operationType: 'GET_DOCUMENT',
          collectionPath: 'documents',
          documentId: docId
        };
        
        try {
          const doc = await executeAuthorizedFirestoreOperation(authorization, operation);
          documents.push({ id: docId, ...doc });
        } catch (error) {
          console.error(`Error fetching document ${docId}:`, error);
          // Continue with other documents
        }
      }
    }
    
    return documents;
  }
  
  // Share a document with another user
  async shareDocument(documentId, userId, permission) {
    // Map permission to relation
    const relationMap = {
      'view': 'viewer',
      'edit': 'editor'
    };
    
    const relation = relationMap[permission];
    if (!relation) {
      throw new Error(`Invalid permission: ${permission}`);
    }
    
    const payload: Types.ManageRelationshipPayload = {
      operation: 'CREATE',
      relationships: [
        {
          resource: {
            objectType: 'document',
            objectId: documentId
          },
          relation: relation,
          subject: {
            object: {
              objectType: 'user',
              objectId: userId
            }
          }
        }
      ]
    };
    
    return manageSpiceDBRelationship(payload);
  }
  
  // Revoke access to a document
  async revokeAccess(documentId, userId, permission) {
    // Map permission to relation
    const relationMap = {
      'view': 'viewer',
      'edit': 'editor'
    };
    
    const relation = relationMap[permission];
    if (!relation) {
      throw new Error(`Invalid permission: ${permission}`);
    }
    
    const payload: Types.ManageRelationshipPayload = {
      operation: 'DELETE',
      relationships: [
        {
          resource: {
            objectType: 'document',
            objectId: documentId
          },
          relation: relation,
          subject: {
            object: {
              objectType: 'user',
              objectId: userId
            }
          }
        }
      ]
    };
    
    return manageSpiceDBRelationship(payload);
  }
}

Implementing Team-Based Access Control

import { 
  manageSpiceDBRelationship, 
  Types 
} from 'auth-cloud-sdk';

class TeamAccessManager {
  // Add a user to a team
  async addUserToTeam(userId, teamId) {
    const payload: Types.ManageRelationshipPayload = {
      operation: 'CREATE',
      relationships: [
        {
          resource: {
            objectType: 'team',
            objectId: teamId
          },
          relation: 'member',
          subject: {
            object: {
              objectType: 'user',
              objectId: userId
            }
          }
        }
      ]
    };
    
    return manageSpiceDBRelationship(payload);
  }
  
  // Grant a team access to a resource
  async grantTeamAccess(teamId, resourceType, resourceId, permission) {
    // Map permission to relation
    const relationMap = {
      'view': 'viewer',
      'edit': 'editor'
    };
    
    const relation = relationMap[permission];
    if (!relation) {
      throw new Error(`Invalid permission: ${permission}`);
    }
    
    const payload: Types.ManageRelationshipPayload = {
      operation: 'CREATE',
      relationships: [
        {
          resource: {
            objectType: resourceType,
            objectId: resourceId
          },
          relation: relation,
          subject: {
            object: {
              objectType: 'team',
              objectId: teamId
            },
            optionalRelation: 'member'
          }
        }
      ]
    };
    
    return manageSpiceDBRelationship(payload);
  }
  
  // Remove a user from a team
  async removeUserFromTeam(userId, teamId) {
    const payload: Types.ManageRelationshipPayload = {
      operation: 'DELETE',
      relationships: [
        {
          resource: {
            objectType: 'team',
            objectId: teamId
          },
          relation: 'member',
          subject: {
            object: {
              objectType: 'user',
              objectId: userId
            }
          }
        }
      ]
    };
    
    return manageSpiceDBRelationship(payload);
  }
}

Error Handling

The Auth Cloud SDK provides consistent error handling across all functions. Here are common errors and how to handle them:

Authentication Errors

These occur when there's no authenticated user or when token retrieval fails.

import { executeAuthorizedFirestoreOperation } from 'auth-cloud-sdk';

try {
  const result = await executeAuthorizedFirestoreOperation(/* ... */);
  // Process result
} catch (error) {
  if (error.message.includes('No authenticated user found')) {
    // Handle unauthenticated user
    console.error('User is not signed in');
    // Redirect to login page
  } else if (error.message.includes('Failed to retrieve Firebase ID token')) {
    // Handle token retrieval failure
    console.error('Authentication token error:', error);
    // Prompt user to sign in again
  } else {
    // Handle other errors
    console.error('Operation failed:', error);
  }
}

Authorization Errors

These occur when a user attempts an operation they're not authorized for.

try {
  const result = await executeAuthorizedFirestoreOperation(/* ... */);
  // Process result
} catch (error) {
  if (error.message.includes('Permission denied') || 
      error.message.includes('not authorized')) {
    // Handle permission denied
    console.error('User does not have permission for this operation');
    // Show appropriate UI message
  } else {
    // Handle other errors
    console.error('Operation failed:', error);
  }
}

Network Errors

These occur when the Cloud Function cannot be reached.

try {
  const result = await executeAuthorizedFirestoreOperation(/* ... */);
  // Process result
} catch (error) {
  if (error.message.includes('Failed to fetch') || 
      error.message.includes('Network error')) {
    // Handle network issues
    console.error('Network error:', error);
    // Show offline message or retry option
  } else {
    // Handle other errors
    console.error('Operation failed:', error);
  }
}

Cloud Function Errors

These are errors returned by the Cloud Functions themselves.

try {
  const result = await executeAuthorizedFirestoreOperation(/* ... */);
  // Process result
} catch (error) {
  if (error.message.includes('Cloud Function Error')) {
    // Extract status code if available
    const statusMatch = error.message.match(/status (\d+)/);
    const statusCode = statusMatch ? parseInt(statusMatch[1]) : null;
    
    if (statusCode === 400) {
      // Handle bad request
      console.error('Invalid request:', error);
    } else if (statusCode === 403) {
      // Handle forbidden
      console.error('Permission denied:', error);
    } else if (statusCode === 404) {
      // Handle not found
      console.error('Resource not found:', error);
    } else if (statusCode === 500) {
      // Handle server error
      console.error('Server error:', error);
    } else {
      // Handle other Cloud Function errors
      console.error('Cloud Function error:', error);
    }
  } else {
    // Handle other errors
    console.error('Operation failed:', error);
  }
}

Best Practices

Security Considerations

  1. Always verify authentication state before making SDK calls:

    if (!firebase.auth().currentUser) {
      // Redirect to login or show authentication required message
      return;
    }
  2. Use the principle of least privilege when defining permissions:

    // Instead of granting editor access when viewer would suffice:
    const relation = userNeedsToEdit ? 'editor' : 'viewer';
  3. Validate inputs before passing them to SDK functions:

    function validateDocumentId(id) {
      if (!id || typeof id !== 'string' || id.trim() === '') {
        throw new Error('Invalid document ID');
      }
      return id;
    }
  4. Don't store sensitive information in client-accessible Firestore documents:

    // Bad practice
    const userData = {
      name: 'User',
      password: 'hashed_password', // Never store this client-side!
      apiKeys: ['secret_key_1', 'secret_key_2'] // Never store this client-side!
    };
       
    // Good practice
    const userData = {
      name: 'User',
      settings: { theme: 'dark', notifications: true }
    };
  5. Implement proper error handling for all SDK calls:

    try {
      await executeAuthorizedFirestoreOperation(/* ... */);
    } catch (error) {
      // Log the error
      console.error('Operation failed:', error);
         
      // Show appropriate user message
      if (error.message.includes('Permission denied')) {
        showUserMessage('You don\'t have permission to perform this action');
      } else {
        showUserMessage('An error occurred. Please try again later.');
      }
         
      // Report to monitoring system if needed
      reportErrorToMonitoring(error);
    }

Performance Optimization

  1. Batch related operations when possible:

    // Instead of multiple separate relationship creations:
    const payload: Types.ManageRelationshipPayload = {
      operation: 'CREATE',
      relationships: [
        { /* relationship 1 */ },
        { /* relationship 2 */ },
        { /* relationship 3 */ }
      ]
    };
    await manageSpiceDBRelationship(payload);
  2. Cache authorization results when appropriate:

    // Cache the list of viewable documents for a short time
    let viewableDocumentsCache = { timestamp: 0, data: [] };
       
    async function getViewableDocuments() {
      const now = Date.now();
      const cacheLifetime = 60 * 1000; // 1 minute
         
      if (now - viewableDocumentsCache.timestamp < cacheLifetime) {
        return viewableDocumentsCache.data;
      }
         
      const result = await lookupAuthorizedResources('document', 'view');
      viewableDocumentsCache = {
        timestamp: now,
        data: result.resourceIds
      };
         
      return result.resourceIds;
    }
  3. Use query constraints to limit data transfer:

    const operation: Types.FirestoreOperation = {
      operationType: 'GET_COLLECTION',
      collectionPath: 'documents',
      queryConstraints: [
        { limit: 10 }, // Only get 10 documents
        { orderBy: 'updatedAt', direction: 'desc' } // Get most recent first
      ]
    };
  4. Implement pagination for large result sets:

    async function getDocumentPage(lastDocId, pageSize = 10) {
      const authorization: Types.Authorization = {
        resourceObjectType: 'collection',
        resourceObjectId: 'documents',
        permission: 'list'
      };
         
      const operation: Types.FirestoreOperation = {
        operationType: 'GET_COLLECTION',
        collectionPath: 'documents',
        queryConstraints: [
          { orderBy: 'createdAt', direction: 'desc' },
          { limit: pageSize }
        ]
      };
         
      // Add startAfter constraint if we have a last document ID
      if (lastDocId) {
        // First get the last document
        const lastDocOp: Types.FirestoreOperation = {
          operationType: 'GET_DOCUMENT',
          collectionPath: 'documents',
          documentId: lastDocId
        };
        const lastDoc = await executeAuthorizedFirestoreOperation(authorization, lastDocOp);
           
        // Then add startAfter to the query constraints
        operation.queryConstraints.push({
          startAfter: lastDoc.createdAt
        });
      }
         
      return executeAuthorizedFirestoreOperation(authorization, operation);
    }

Integration Patterns

  1. Implement a service layer to abstract SDK calls:

    // documents.service.ts
    import { executeAuthorizedFirestoreOperation, Types } from 'auth-cloud-sdk';
       
    export class DocumentService {
      async getDocument(id) {
        const authorization: Types.Authorization = {
          resourceObjectType: 'document',
          resourceObjectId: id,
          permission: 'read'
        };
           
        const operation: Types.FirestoreOperation = {
          operationType: 'GET_DOCUMENT',
          collectionPath: 'documents',
          documentId: id
        };
           
        return executeAuthorizedFirestoreOperation(authorization, operation);
      }
         
      // Other document-related methods...
    }
  2. Use dependency injection for testability:

    // In a React component with dependency injection
    function DocumentViewer({ documentId, documentService }) {
      const [document, setDocument] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
         
      useEffect(() => {
        async function loadDocument() {
          try {
            const doc = await documentService.getDocument(documentId);
            setDocument(doc);
          } catch (err) {
            setError(err);
          } finally {
            setLoading(false);
          }
        }
           
        loadDocument();
      }, [documentId, documentService]);
         
      // Render component...
    }
  3. Implement retry logic for transient errors:

    async function executeWithRetry(fn, maxRetries = 3, delay = 1000) {
      let lastError;
         
      for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
          return await fn();
        } catch (error) {
          lastError = error;
             
          // Only retry for certain errors
          if (!isRetryableError(error) || attempt === maxRetries) {
            throw error;
          }
             
          // Wait before retrying
          await new Promise(resolve => setTimeout(resolve, delay * attempt));
        }
      }
         
      throw lastError;
    }
       
    function isRetryableError(error) {
      // Determine if this error should be retried
      return error.message.includes('network error') || 
             error.message.includes('timeout') ||
             error.message.includes('ECONNRESET');
    }
       
    // Usage
    const result = await executeWithRetry(() => 
      executeAuthorizedFirestoreOperation(authorization, operation)
    );

Troubleshooting

Common Issues and Solutions

"Cloud Function endpoints not configured"

Problem: You're seeing an error about endpoints not being configured.

Solution: Ensure you've called setCloudFunctionEndpoints before using any SDK functions:

import { setCloudFunctionEndpoints } from 'auth-cloud-sdk';

// Call this early in your application initialization
setCloudFunctionEndpoints({
  executeAuthorizedFirestoreOperation: 'https://your-region-your-project.cloudfunctions.net/executeAuthorizedFirestoreOperation',
  manageSpiceDBRelationship: 'https://your-region-your-project.cloudfunctions.net/manageSpiceDBRelationship',
  writeSpiceDBSchema: 'https://your-region-your-project.cloudfunctions.net/writeSpiceDBSchema',
  lookupAuthorizedResources: 'https://your-region-your-project.cloudfunctions.net/lookupAuthorizedResources'
});

"No authenticated user found"

Problem: You're trying to use SDK functions without a signed-in user.

Solution: Ensure the user is authenticated before making SDK calls:

import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

// Sign in the user first
const auth = getAuth();
await signInWithEmailAndPassword(auth, email, password);

// Now you can use SDK functions
const result = await executeAuthorizedFirestoreOperation(/* ... */);

"Permission denied" or "not authorized"

Problem: The user doesn't have the required permissions for the operation.

Solution: Check and update the user's permissions:

import { manageSpiceDBRelationship, Types } from 'auth-cloud-sdk';

// Grant the necessary permission
const payload: Types.ManageRelationshipPayload = {
  operation: 'CREATE',
  relationships: [
    {
      resource: {
        objectType: 'document',
        objectId: documentId
      },
      relation: 'viewer', // or 'editor', 'owner', etc.
      subject: {
        object: {
          objectType: 'user',
          objectId: userId
        }
      }
    }
  ]
};

await manageSpiceDBRelationship(payload);

"Failed to fetch" or Network Errors

Problem: The SDK can't reach the Cloud Functions.

Solution: Check your network connection and Cloud Function URLs:

  1. Verify the Cloud Function URLs are correct
  2. Ensure the Cloud Functions are deployed and running
  3. Check for CORS issues if calling from a browser
  4. Verify the Cloud Functions are in the same region as your application for best performance

"Invalid request" (400 errors)

Problem: The request to the Cloud Function is malformed.

Solution: Check your parameters and ensure they match the expected format:

// Correct format for authorization
const authorization: Types.Authorization = {
  resourceObjectType: 'document', // Required
  resourceObjectId: documentId,   // Required
  permission: 'read'              // Required
};

// Correct format for Firestore operation
const operation: Types.FirestoreOperation = {
  operationType: 'GET_DOCUMENT',  // Required
  collectionPath: 'documents',    // Required
  documentId: documentId          // Required for GET_DOCUMENT
};

Debugging Tips

  1. Enable verbose logging:

    // Add this early in your application
    const DEBUG = true;
       
    function debugLog(...args) {
      if (DEBUG) {
        console.log('[Auth Cloud SDK]', ...args);
      }
    }
       
    // Use throughout your code
    debugLog('Calling executeAuthorizedFirestoreOperation with:', authorization, operation);
  2. Check Firebase Authentication state:

    import { getAuth, onAuthStateChanged } from 'firebase/auth';
       
    const auth = getAuth();
    onAuthStateChanged(auth, (user) => {
      if (user) {
        console.log('User is signed in:', user.uid);
        user.getIdTokenResult(true).then(idTokenResult => {
          console.log('User claims:', idTokenResult.claims);
        });
      } else {
        console.log('No user is signed in');
      }
    });
  3. Verify Cloud Function endpoints:

    import { getCloudFunctionEndpoints } from 'auth-cloud-sdk';
       
    try {
      const endpoints = getCloudFunctionEndpoints();
      console.log('Configured endpoints:', endpoints);
    } catch (error) {
      console.error('Endpoints not configured:', error);
    }
  4. Test Cloud Functions directly:

    // Test a Cloud Function directly with fetch
    async function testCloudFunction() {
      const auth = getAuth();
      const user = auth.currentUser;
         
      if (!user) {
        console.error('No user signed in');
        return;
      }
         
      const idToken = await user.getIdToken();
      const endpoint = 'https://your-region-your-project.cloudfunctions.net/lookupAuthorizedResources';
         
      try {
        const response = await fetch(endpoint, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            firebaseToken: idToken,
            resourceObjectType: 'document',
            permission: 'view'
          })
        });
           
        if (!response.ok) {
          const errorText = await response.text();
          console.error(`Error ${response.status}:`, errorText);
        } else {
          const result = await response.json();
          console.log('Success:', result);
        }
      } catch (error) {
        console.error('Fetch error:', error);
      }
    }

FAQ

General Questions

Q: Do I need to initialize Firebase before using the SDK?

A: Yes, you must initialize Firebase in your application before using the SDK. The SDK relies on the Firebase Auth instance to get the current user and their ID token.

Q: Can I use the SDK with any Firebase project?

A: Yes, but you need to deploy the corresponding Cloud Functions to your Firebase project. The SDK is designed to work with specific Cloud Functions that handle authorization and Firestore operations.

Q: Does the SDK work with Firebase Emulator Suite?

A: Yes, you can use the SDK with Firebase Emulator Suite for local development. Just configure the SDK with the local URLs of your emulated Cloud Functions.

setCloudFunctionEndpoints({
  executeAuthorizedFirestoreOperation: 'http://localhost:5001/your-project/us-central1/executeAuthorizedFirestoreOperation',
  manageSpiceDBRelationship: 'http://localhost:5001/your-project/us-central1/manageSpiceDBRelationship',
  writeSpiceDBSchema: 'http://localhost:5001/your-project/us-central1/writeSpiceDBSchema',
  lookupAuthorizedResources: 'http://localhost:5001/your-project/us-central1/lookupAuthorizedResources'
});

Authentication Questions

Q: How does the SDK handle authentication?

A: The SDK uses Firebase Authentication. It retrieves the current user's ID token and sends it to the Cloud Functions for verification. The Cloud Functions then use this token to authenticate the user and perform the requested operations.

Q: What happens if the user's token expires?

A: The SDK automatically requests a fresh token when needed. If the token has expired, the getFirebaseIdToken function will force a refresh.

Q: Can I use custom authentication with the SDK?

A: The SDK is designed to work with Firebase Authentication. If you're using custom authentication, you'll need to integrate it with Firebase Auth using Custom Auth Providers.

Authorization Questions

Q: What is SpiceDB and why is it used?

A: SpiceDB (formerly Authzed) is an open-source, fine-grained permissions system. It's used to manage authorization relationships and check permissions. The SDK uses SpiceDB through Cloud Functions to provide robust authorization capabilities.

Q: How do I define permissions in SpiceDB?

A: Permissions in SpiceDB are defined through a schema. You can write and update this schema using the writeSpiceDBSchema function. Here's an example schema:

definition user {}

definition document {
  relation viewer: user
  relation editor: user
  relation owner: user
  
  permission view = viewer + editor + owner
  permission edit = editor + owner
  permission delete = owner
}

Q: Can I use role-based access control (RBAC) with the SDK?

A: Yes, you can implement RBAC using SpiceDB relationships. For example:

// Define roles in your SpiceDB schema
const schema = `
  definition user {}
  
  definition role {
    relation member: user
  }
  
  definition document {
    relation viewer: user | role#member
    relation editor: user | role#member
    
    permission view = viewer
    permission edit = editor
  }
`;

// Assign a user to a role
const assignRolePayload: Types.ManageRelationshipPayload = {
  operation: 'CREATE',
  relationships: [
    {
      resource: {
        objectType: 'role',
        objectId: 'admin'
      },
      relation: 'member',
      subject: {
        object: {
          objectType: 'user',
          objectId: userId
        }
      }
    }
  ]
};

// Grant a role access to a document
const grantRoleAccessPayload: Types.ManageRelationshipPayload = {
  operation: 'CREATE',
  relationships: [
    {
      resource: {
        objectType: 'document',
        objectId: documentId
      },
      relation: 'editor',
      subject: {
        object: {
          objectType: 'role',
          objectId: 'admin'
        }
      }
    }
  ]
};

Firestore Questions

Q: Does the SDK replace the Firestore SDK?

A: No, the SDK complements the Firestore SDK by adding authorization checks. It's designed for operations that require permission checks, but you can still use the regular Firestore SDK for operations that don't need authorization.

Q: Can I use the SDK with Firestore Security Rules?

A: Yes, but they serve different purposes. Firestore Security Rules provide client-side security, while the SDK provides server-side security through Cloud Functions. You can use both together for enhanced security.

Q: How does the SDK handle Firestore transactions?

A: The SDK doesn't directly support Firestore transactions. For operations that require transactions, you should create a dedicated Cloud Function that performs the transaction and call it through the SDK.

Development and Deployment Questions

Q: How do I update the SDK to a new version?

A: Update the SDK using npm or yarn:

npm update auth-cloud-sdk
# or
yarn upgrade auth-cloud-sdk

Q: Can I contribute to the SDK?

A: Yes, contributions are welcome. Check the repository for contribution guidelines.

Q: How do I report bugs or request features?

A: Report bugs and request features through the repository's issue tracker.


Now that you have a comprehensive understanding of the Auth Cloud SDK, you can build secure, authorized applications with confidence. The SDK provides a robust foundation for implementing complex authorization patterns while keeping your code clean and maintainable.