@prmichaelsen/firebase-admin-sdk-v8 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Agent Context Protocol (ACP)
2
2
 
3
- **Also Known As**: The Agent Directory Pattern
4
- **Version**: 1.0.3
5
- **Created**: 2026-02-11
6
- **Status**: Production Pattern
3
+ **Also Known As**: The Agent Directory Pattern
4
+ **Version**: 1.4.3
5
+ **Created**: 2026-02-11
6
+ **Status**: Production Pattern
7
7
 
8
8
  ---
9
9
 
@@ -81,6 +81,14 @@ ACP solves these by:
81
81
  project-root/
82
82
  ├── AGENT.md # This file - ACP documentation
83
83
  ├── agent/ # Agent directory (ACP structure)
84
+ │ ├── commands/ # Command system
85
+ │ │ ├── .gitkeep
86
+ │ │ ├── command.template.md # Command template
87
+ │ │ ├── acp.init.md # @acp-init
88
+ │ │ ├── acp.proceed.md # @acp-proceed
89
+ │ │ ├── acp.status.md # @acp-status
90
+ │ │ └── ... # More commands
91
+ │ │
84
92
  │ ├── design/ # Design documents
85
93
  │ │ ├── .gitkeep
86
94
  │ │ ├── requirements.md # Core requirements
@@ -551,6 +559,91 @@ The Agent Pattern represents a **paradigm shift** in how we approach AI-assisted
551
559
 
552
560
  ---
553
561
 
562
+ ## ACP Commands
563
+
564
+ ACP supports a command system for common workflows. Commands are file-based triggers that provide standardized, discoverable interfaces for ACP operations.
565
+
566
+ ### What are ACP Commands?
567
+
568
+ Commands are markdown files in [`agent/commands/`](agent/commands/) that contain step-by-step instructions for AI agents. Instead of typing long prompts like "AGENT.md: Initialize", you can reference command files like `@acp.init` to trigger specific workflows.
569
+
570
+ **Benefits**:
571
+ - **Discoverable**: Browse [`agent/commands/`](agent/commands/) to see all available commands
572
+ - **Consistent**: All commands follow the same structure
573
+ - **Extensible**: Create custom commands for your project
574
+ - **Self-Documenting**: Each command file contains complete documentation
575
+ - **Autocomplete-Friendly**: Type `@acp.` to see all ACP commands
576
+
577
+ ### Core Commands
578
+
579
+ Core ACP commands use the `acp.` prefix and are available in [`agent/commands/`](agent/commands/):
580
+
581
+ - **[`@acp.init`](agent/commands/acp.init.md)** - Initialize agent context (replaces "AGENT.md: Initialize")
582
+ - **[`@acp.proceed`](agent/commands/acp.proceed.md)** - Continue with next task (replaces "AGENT.md: Proceed")
583
+ - **[`@acp.status`](agent/commands/acp.status.md)** - Display project status
584
+ - **[`@acp.version-check`](agent/commands/acp.version-check.md)** - Show current ACP version
585
+ - **[`@acp.version-check-for-updates`](agent/commands/acp.version-check-for-updates.md)** - Check for ACP updates
586
+ - **[`@acp.version-update`](agent/commands/acp.version-update.md)** - Update ACP to latest version
587
+
588
+ ### Command Invocation
589
+
590
+ Commands are invoked using the `@` syntax with dot notation:
591
+
592
+ ```
593
+ @acp.init → agent/commands/acp.init.md
594
+ @acp.proceed → agent/commands/acp.proceed.md
595
+ @acp.status → agent/commands/acp.status.md
596
+ @deploy.production → agent/commands/deploy.production.md
597
+ ```
598
+
599
+ **Format**: `@{namespace}.{action}` resolves to `agent/commands/{namespace}.{action}.md`
600
+
601
+ ### Creating Custom Commands
602
+
603
+ To create custom commands for your project:
604
+
605
+ 1. **Choose a namespace** (e.g., `deploy`, `test`, `custom`)
606
+ - ⚠️ The `acp` namespace is reserved for core commands
607
+ - Use descriptive, single-word namespaces
608
+
609
+ 2. **Copy the command template**:
610
+ ```bash
611
+ cp agent/commands/command.template.md agent/commands/{namespace}.{action}.md
612
+ ```
613
+
614
+ 3. **Fill in the template sections**:
615
+ - Purpose and description
616
+ - Prerequisites
617
+ - Step-by-step instructions
618
+ - Verification checklist
619
+ - Examples and troubleshooting
620
+
621
+ 4. **Invoke your command**: `@{namespace}.{action}`
622
+
623
+ **Example**: Creating a deployment command:
624
+ ```bash
625
+ # Create the command file
626
+ cp agent/commands/command.template.md agent/commands/deploy.production.md
627
+
628
+ # Edit the file with your deployment steps
629
+ # ...
630
+
631
+ # Invoke it
632
+ @deploy.production
633
+ ```
634
+
635
+ ### Command Template
636
+
637
+ See [`agent/commands/command.template.md`](agent/commands/command.template.md) for the complete command template with all sections and examples.
638
+
639
+ ### Installing Third-Party Commands
640
+
641
+ Use `@acp.install` to install command packages from git repositories (available in future release).
642
+
643
+ **Security Note**: Third-party commands can instruct agents to modify files and execute scripts. Always review command files before installation.
644
+
645
+ ---
646
+
554
647
  ## Sample Prompts for Using ACP
555
648
 
556
649
  ### Initialize Prompt
@@ -782,7 +875,17 @@ Run ./agent/scripts/uninstall.sh to remove all ACP files (agent/ directory and A
782
875
  - Update percentages
783
876
  - Add recent work notes
784
877
 
785
- 7. **NEVER handle secrets or sensitive data**
878
+ 7. **CRITICAL: Always update CHANGELOG.md for version changes**
879
+ - ❌ **DO NOT** commit version changes without updating CHANGELOG.md
880
+ - ❌ **DO NOT** forget to update version numbers in all project files
881
+ - ✅ **DO** use [`@git.commit`](agent/commands/git.commit.md) for version-aware commits
882
+ - ✅ **DO** detect version impact: major (breaking), minor (features), patch (fixes)
883
+ - ✅ **DO** update CHANGELOG.md with clear, user-focused descriptions
884
+ - ✅ **DO** update all version files (package.json, AGENT.md, etc.)
885
+ - ✅ **DO** use Conventional Commits format for commit messages
886
+ - **Rationale**: CHANGELOG.md is the primary communication tool for users. Every version change must be documented with clear descriptions of what changed, why it changed, and how it affects users. Forgetting to update CHANGELOG.md breaks the project's version history and makes it impossible for users to understand what changed between versions.
887
+
888
+ 8. **NEVER handle secrets or sensitive data**
786
889
  - ❌ **DO NOT** read `.env` files, `.env.local`, or any environment files
787
890
  - ❌ **DO NOT** read files containing API keys, tokens, passwords, or credentials
788
891
  - ❌ **DO NOT** include secrets in messages, documentation, or code examples
@@ -793,6 +896,14 @@ Run ./agent/scripts/uninstall.sh to remove all ACP files (agent/ directory and A
793
896
  - ✅ **DO** create `.env.example` files with placeholder values only
794
897
  - **Rationale**: Secrets must never be exposed in chat logs, documentation, or version control. Agents should treat all credential files as off-limits to prevent accidental exposure.
795
898
 
899
+ 9. **CRITICAL: Respect user's intentional file edits**
900
+ - ❌ **DO NOT** assume missing content needs to be added back
901
+ - ❌ **DO NOT** revert changes without confirming with user
902
+ - ✅ **DO** read files before editing to see current state
903
+ - ✅ **DO** ask user if unexpected changes were intentional
904
+ - ✅ **DO** confirm before reverting user's manual edits
905
+ - **Rationale**: If you read a file and it is missing contents or has changed contents (i.e., it does not contain what you expect), assume or confirm with the user if they made intentional updates that you should not revert. Do not assume "The file is missing <xyz>, I need to add it back". The user may have edited files manually with intention.
906
+
796
907
  ---
797
908
 
798
909
  ## Best Practices
@@ -882,7 +993,7 @@ This repository is actively maintained with improvements to the ACP methodology
882
993
  ./agent/scripts/update.sh
883
994
 
884
995
  # Or download and run directly
885
- curl -fsSL https://raw.githubusercontent.com/prmichaelsen/agent-context-protocol/mainlin./agent/scripts/update.sh | bash
996
+ curl -fsSL https://raw.githubusercontent.com/prmichaelsen/agent-context-protocol/mainline/agent/scripts/update.sh | bash
886
997
  ```
887
998
 
888
999
  The update script will:
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.5.0] - 2026-02-19
11
+
12
+ ### Added
13
+ - **User Management APIs**: Complete user management functionality via Firebase Identity Toolkit REST API
14
+ - `getUserByEmail()` - Look up users by email address
15
+ - `getUserByUid()` - Look up users by UID
16
+ - `createUser()` - Create new Firebase users with email, password, display name, etc.
17
+ - `updateUser()` - Update existing user properties (email, password, display name, photo URL, etc.)
18
+ - `deleteUser()` - Delete Firebase users
19
+ - `listUsers()` - List all users with pagination support (up to 1000 per page)
20
+ - `setCustomUserClaims()` - Set custom claims for role-based access control
21
+ - New type definitions: `UserRecord`, `CreateUserRequest`, `UpdateUserRequest`, `ListUsersResult`
22
+ - 30 comprehensive unit tests for user management (91.66% coverage)
23
+ - 18 E2E tests for user management (complete lifecycle testing)
24
+ - Complete user management documentation in README with examples
25
+ - Feature comparison table updated to show user management support
26
+
27
+ ### Changed
28
+ - Total unit tests increased from 433 to 463 (+30 tests)
29
+ - Total E2E tests increased from 105 to 123 (+18 tests)
30
+ - Updated feature list to include user management
31
+
32
+ ### Notes
33
+ - `listUsers()` E2E tests are skipped as the REST API endpoint availability varies by Firebase project configuration
34
+
10
35
  ## [2.4.0] - 2026-02-15
11
36
 
12
37
  ### Added
package/README.md CHANGED
@@ -16,6 +16,7 @@ This library provides Firebase Admin SDK functionality for Cloudflare Workers an
16
16
  - ✅ **JWT Token Generation** - Service account authentication
17
17
  - ✅ **ID Token Verification** - Verify Firebase ID tokens (supports v9 and v10 formats)
18
18
  - ✅ **Session Cookies** - Create and verify long-lived session cookies (up to 14 days)
19
+ - ✅ **User Management** - Create, read, update, delete users via Admin API
19
20
  - ✅ **Firebase v10 Compatible** - Supports both old and new token issuer formats
20
21
  - ✅ **Firestore REST API** - Full CRUD operations via REST
21
22
  - ✅ **Field Value Operations** - increment, arrayUnion, arrayRemove, serverTimestamp, delete
@@ -173,6 +174,97 @@ const user = await getUserFromToken(idToken);
173
174
  // Returns: { uid, email, emailVerified, displayName, photoURL }
174
175
  ```
175
176
 
177
+ ### User Management
178
+
179
+ #### `getUserByEmail(email: string): Promise<UserRecord | null>`
180
+
181
+ Look up a Firebase user by email address.
182
+
183
+ ```typescript
184
+ const user = await getUserByEmail('user@example.com');
185
+ if (user) {
186
+ console.log('User ID:', user.uid);
187
+ console.log('Email verified:', user.emailVerified);
188
+ }
189
+ ```
190
+
191
+ #### `getUserByUid(uid: string): Promise<UserRecord | null>`
192
+
193
+ Look up a Firebase user by UID.
194
+
195
+ ```typescript
196
+ const user = await getUserByUid('user123');
197
+ if (user) {
198
+ console.log('Email:', user.email);
199
+ console.log('Display name:', user.displayName);
200
+ }
201
+ ```
202
+
203
+ #### `createUser(properties: CreateUserRequest): Promise<UserRecord>`
204
+
205
+ Create a new Firebase user.
206
+
207
+ ```typescript
208
+ const newUser = await createUser({
209
+ email: 'newuser@example.com',
210
+ password: 'securePassword123',
211
+ displayName: 'New User',
212
+ emailVerified: false,
213
+ });
214
+ console.log('Created user:', newUser.uid);
215
+ ```
216
+
217
+ #### `updateUser(uid: string, properties: UpdateUserRequest): Promise<UserRecord>`
218
+
219
+ Update an existing Firebase user.
220
+
221
+ ```typescript
222
+ const updatedUser = await updateUser('user123', {
223
+ displayName: 'Updated Name',
224
+ photoURL: 'https://example.com/photo.jpg',
225
+ emailVerified: true,
226
+ });
227
+ ```
228
+
229
+ #### `deleteUser(uid: string): Promise<void>`
230
+
231
+ Delete a Firebase user.
232
+
233
+ ```typescript
234
+ await deleteUser('user123');
235
+ ```
236
+
237
+ #### `listUsers(maxResults?: number, pageToken?: string): Promise<ListUsersResult>`
238
+
239
+ List all users with pagination.
240
+
241
+ ```typescript
242
+ // List first 100 users
243
+ const result = await listUsers(100);
244
+ console.log('Users:', result.users.length);
245
+
246
+ // Get next page
247
+ if (result.pageToken) {
248
+ const nextPage = await listUsers(100, result.pageToken);
249
+ }
250
+ ```
251
+
252
+ #### `setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>`
253
+
254
+ Set custom claims on a user's ID token for role-based access control.
255
+
256
+ ```typescript
257
+ // Set custom claims
258
+ await setCustomUserClaims('user123', {
259
+ role: 'admin',
260
+ premium: true,
261
+ permissions: ['read', 'write', 'delete'],
262
+ });
263
+
264
+ // Clear custom claims
265
+ await setCustomUserClaims('user123', null);
266
+ ```
267
+
176
268
  ### Firestore - Basic Operations
177
269
 
178
270
  #### `setDocument(collectionPath, documentId, data, options?): Promise<void>`
@@ -619,7 +711,7 @@ For better query performance:
619
711
  | ID Token Verification | ✅ | Supports v9 and v10 token formats |
620
712
  | Custom Token Creation | ✅ | createCustomToken() |
621
713
  | Custom Token Exchange | ✅ | signInWithCustomToken() |
622
- | User Management | | Not yet implemented |
714
+ | User Management | | Create, read, update, delete, list users |
623
715
  | Firestore CRUD | ✅ | Full support |
624
716
  | Firestore Queries | ✅ | where, orderBy, limit, cursors |
625
717
  | Firestore Batch | ✅ | Up to 500 operations |
package/dist/index.d.mts CHANGED
@@ -183,6 +183,81 @@ interface BatchWriteResult {
183
183
  updateTime: string;
184
184
  }>;
185
185
  }
186
+ /**
187
+ * Firebase user record
188
+ */
189
+ interface UserRecord {
190
+ /** User's unique ID */
191
+ uid: string;
192
+ /** User's email address */
193
+ email?: string;
194
+ /** Whether the email is verified */
195
+ emailVerified: boolean;
196
+ /** User's display name */
197
+ displayName?: string;
198
+ /** User's photo URL */
199
+ photoURL?: string;
200
+ /** User's phone number */
201
+ phoneNumber?: string;
202
+ /** Whether the user is disabled */
203
+ disabled: boolean;
204
+ /** User metadata (creation and last sign-in times) */
205
+ metadata: {
206
+ creationTime: string;
207
+ lastSignInTime: string;
208
+ };
209
+ /** Provider-specific user information */
210
+ providerData: UserInfo[];
211
+ /** Custom claims set on the user */
212
+ customClaims?: Record<string, any>;
213
+ }
214
+ /**
215
+ * Request to create a new user
216
+ */
217
+ interface CreateUserRequest {
218
+ /** User's email address */
219
+ email?: string;
220
+ /** Whether the email should be marked as verified */
221
+ emailVerified?: boolean;
222
+ /** User's phone number */
223
+ phoneNumber?: string;
224
+ /** User's password */
225
+ password?: string;
226
+ /** User's display name */
227
+ displayName?: string;
228
+ /** User's photo URL */
229
+ photoURL?: string;
230
+ /** Whether the user should be disabled */
231
+ disabled?: boolean;
232
+ }
233
+ /**
234
+ * Request to update an existing user
235
+ */
236
+ interface UpdateUserRequest {
237
+ /** User's email address */
238
+ email?: string;
239
+ /** Whether the email should be marked as verified */
240
+ emailVerified?: boolean;
241
+ /** User's phone number */
242
+ phoneNumber?: string;
243
+ /** User's password */
244
+ password?: string;
245
+ /** User's display name */
246
+ displayName?: string;
247
+ /** User's photo URL */
248
+ photoURL?: string;
249
+ /** Whether the user should be disabled */
250
+ disabled?: boolean;
251
+ }
252
+ /**
253
+ * Result of listing users
254
+ */
255
+ interface ListUsersResult {
256
+ /** Array of user records */
257
+ users: UserRecord[];
258
+ /** Token for fetching the next page (if available) */
259
+ pageToken?: string;
260
+ }
186
261
 
187
262
  /**
188
263
  * Firebase Admin SDK v8 - Configuration
@@ -395,6 +470,55 @@ declare function verifySessionCookie(sessionCookie: string, checkRevoked?: boole
395
470
  */
396
471
  declare function getAuth(): any;
397
472
 
473
+ /**
474
+ * Firebase User Management APIs
475
+ * Provides user management operations using Firebase Identity Toolkit REST API
476
+ */
477
+
478
+ /**
479
+ * Look up a Firebase user by email address
480
+ * @param email - User's email address
481
+ * @returns User record or null if not found
482
+ */
483
+ declare function getUserByEmail(email: string): Promise<UserRecord | null>;
484
+ /**
485
+ * Look up a Firebase user by UID
486
+ * @param uid - User's unique ID
487
+ * @returns User record or null if not found
488
+ */
489
+ declare function getUserByUid(uid: string): Promise<UserRecord | null>;
490
+ /**
491
+ * Create a new Firebase user
492
+ * @param properties - User properties (email, password, displayName, etc.)
493
+ * @returns Created user record
494
+ */
495
+ declare function createUser(properties: CreateUserRequest): Promise<UserRecord>;
496
+ /**
497
+ * Update an existing Firebase user
498
+ * @param uid - User's unique ID
499
+ * @param properties - Properties to update (email, password, displayName, etc.)
500
+ * @returns Updated user record
501
+ */
502
+ declare function updateUser(uid: string, properties: UpdateUserRequest): Promise<UserRecord>;
503
+ /**
504
+ * Delete a Firebase user
505
+ * @param uid - User's unique ID
506
+ */
507
+ declare function deleteUser(uid: string): Promise<void>;
508
+ /**
509
+ * List all users with pagination
510
+ * @param maxResults - Maximum number of users to return (default: 1000, max: 1000)
511
+ * @param pageToken - Token for next page
512
+ * @returns List of users and next page token
513
+ */
514
+ declare function listUsers(maxResults?: number, pageToken?: string): Promise<ListUsersResult>;
515
+ /**
516
+ * Set custom claims on a user's ID token
517
+ * @param uid - User's unique ID
518
+ * @param customClaims - Custom claims object (max 1000 bytes when serialized)
519
+ */
520
+ declare function setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>;
521
+
398
522
  /**
399
523
  * Firebase Admin SDK v8 - Firestore CRUD Operations
400
524
  * All Firestore document operations using REST API
@@ -979,4 +1103,4 @@ declare function getAdminAccessToken(): Promise<string>;
979
1103
  */
980
1104
  declare function clearTokenCache(): void;
981
1105
 
982
- export { type BatchWrite, type BatchWriteResult, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, queryDocuments, setDocument, signInWithCustomToken, updateDocument, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1106
+ export { type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, setCustomUserClaims, setDocument, signInWithCustomToken, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.d.ts CHANGED
@@ -183,6 +183,81 @@ interface BatchWriteResult {
183
183
  updateTime: string;
184
184
  }>;
185
185
  }
186
+ /**
187
+ * Firebase user record
188
+ */
189
+ interface UserRecord {
190
+ /** User's unique ID */
191
+ uid: string;
192
+ /** User's email address */
193
+ email?: string;
194
+ /** Whether the email is verified */
195
+ emailVerified: boolean;
196
+ /** User's display name */
197
+ displayName?: string;
198
+ /** User's photo URL */
199
+ photoURL?: string;
200
+ /** User's phone number */
201
+ phoneNumber?: string;
202
+ /** Whether the user is disabled */
203
+ disabled: boolean;
204
+ /** User metadata (creation and last sign-in times) */
205
+ metadata: {
206
+ creationTime: string;
207
+ lastSignInTime: string;
208
+ };
209
+ /** Provider-specific user information */
210
+ providerData: UserInfo[];
211
+ /** Custom claims set on the user */
212
+ customClaims?: Record<string, any>;
213
+ }
214
+ /**
215
+ * Request to create a new user
216
+ */
217
+ interface CreateUserRequest {
218
+ /** User's email address */
219
+ email?: string;
220
+ /** Whether the email should be marked as verified */
221
+ emailVerified?: boolean;
222
+ /** User's phone number */
223
+ phoneNumber?: string;
224
+ /** User's password */
225
+ password?: string;
226
+ /** User's display name */
227
+ displayName?: string;
228
+ /** User's photo URL */
229
+ photoURL?: string;
230
+ /** Whether the user should be disabled */
231
+ disabled?: boolean;
232
+ }
233
+ /**
234
+ * Request to update an existing user
235
+ */
236
+ interface UpdateUserRequest {
237
+ /** User's email address */
238
+ email?: string;
239
+ /** Whether the email should be marked as verified */
240
+ emailVerified?: boolean;
241
+ /** User's phone number */
242
+ phoneNumber?: string;
243
+ /** User's password */
244
+ password?: string;
245
+ /** User's display name */
246
+ displayName?: string;
247
+ /** User's photo URL */
248
+ photoURL?: string;
249
+ /** Whether the user should be disabled */
250
+ disabled?: boolean;
251
+ }
252
+ /**
253
+ * Result of listing users
254
+ */
255
+ interface ListUsersResult {
256
+ /** Array of user records */
257
+ users: UserRecord[];
258
+ /** Token for fetching the next page (if available) */
259
+ pageToken?: string;
260
+ }
186
261
 
187
262
  /**
188
263
  * Firebase Admin SDK v8 - Configuration
@@ -395,6 +470,55 @@ declare function verifySessionCookie(sessionCookie: string, checkRevoked?: boole
395
470
  */
396
471
  declare function getAuth(): any;
397
472
 
473
+ /**
474
+ * Firebase User Management APIs
475
+ * Provides user management operations using Firebase Identity Toolkit REST API
476
+ */
477
+
478
+ /**
479
+ * Look up a Firebase user by email address
480
+ * @param email - User's email address
481
+ * @returns User record or null if not found
482
+ */
483
+ declare function getUserByEmail(email: string): Promise<UserRecord | null>;
484
+ /**
485
+ * Look up a Firebase user by UID
486
+ * @param uid - User's unique ID
487
+ * @returns User record or null if not found
488
+ */
489
+ declare function getUserByUid(uid: string): Promise<UserRecord | null>;
490
+ /**
491
+ * Create a new Firebase user
492
+ * @param properties - User properties (email, password, displayName, etc.)
493
+ * @returns Created user record
494
+ */
495
+ declare function createUser(properties: CreateUserRequest): Promise<UserRecord>;
496
+ /**
497
+ * Update an existing Firebase user
498
+ * @param uid - User's unique ID
499
+ * @param properties - Properties to update (email, password, displayName, etc.)
500
+ * @returns Updated user record
501
+ */
502
+ declare function updateUser(uid: string, properties: UpdateUserRequest): Promise<UserRecord>;
503
+ /**
504
+ * Delete a Firebase user
505
+ * @param uid - User's unique ID
506
+ */
507
+ declare function deleteUser(uid: string): Promise<void>;
508
+ /**
509
+ * List all users with pagination
510
+ * @param maxResults - Maximum number of users to return (default: 1000, max: 1000)
511
+ * @param pageToken - Token for next page
512
+ * @returns List of users and next page token
513
+ */
514
+ declare function listUsers(maxResults?: number, pageToken?: string): Promise<ListUsersResult>;
515
+ /**
516
+ * Set custom claims on a user's ID token
517
+ * @param uid - User's unique ID
518
+ * @param customClaims - Custom claims object (max 1000 bytes when serialized)
519
+ */
520
+ declare function setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>;
521
+
398
522
  /**
399
523
  * Firebase Admin SDK v8 - Firestore CRUD Operations
400
524
  * All Firestore document operations using REST API
@@ -979,4 +1103,4 @@ declare function getAdminAccessToken(): Promise<string>;
979
1103
  */
980
1104
  declare function clearTokenCache(): void;
981
1105
 
982
- export { type BatchWrite, type BatchWriteResult, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, queryDocuments, setDocument, signInWithCustomToken, updateDocument, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1106
+ export { type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, setCustomUserClaims, setDocument, signInWithCustomToken, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.js CHANGED
@@ -208,8 +208,10 @@ __export(index_exports, {
208
208
  countDocuments: () => countDocuments,
209
209
  createCustomToken: () => createCustomToken,
210
210
  createSessionCookie: () => createSessionCookie,
211
+ createUser: () => createUser,
211
212
  deleteDocument: () => deleteDocument,
212
213
  deleteFile: () => deleteFile,
214
+ deleteUser: () => deleteUser,
213
215
  downloadFile: () => downloadFile,
214
216
  fileExists: () => fileExists,
215
217
  generateSignedUrl: () => generateSignedUrl,
@@ -220,15 +222,20 @@ __export(index_exports, {
220
222
  getFileMetadata: () => getFileMetadata,
221
223
  getProjectId: () => getProjectId,
222
224
  getServiceAccount: () => getServiceAccount,
225
+ getUserByEmail: () => getUserByEmail,
226
+ getUserByUid: () => getUserByUid,
223
227
  getUserFromToken: () => getUserFromToken,
224
228
  initializeApp: () => initializeApp,
225
229
  iterateCollection: () => iterateCollection,
226
230
  listDocuments: () => listDocuments,
227
231
  listFiles: () => listFiles,
232
+ listUsers: () => listUsers,
228
233
  queryDocuments: () => queryDocuments,
234
+ setCustomUserClaims: () => setCustomUserClaims,
229
235
  setDocument: () => setDocument,
230
236
  signInWithCustomToken: () => signInWithCustomToken,
231
237
  updateDocument: () => updateDocument,
238
+ updateUser: () => updateUser,
232
239
  uploadFile: () => uploadFile,
233
240
  uploadFileResumable: () => uploadFileResumable,
234
241
  verifyIdToken: () => verifyIdToken,
@@ -330,23 +337,41 @@ async function importPublicKeyFromX509(pem) {
330
337
  }
331
338
 
332
339
  // src/auth.ts
333
- var publicKeysCache = null;
334
- var publicKeysCacheExpiry = 0;
340
+ var idTokenKeysCache = null;
341
+ var idTokenKeysCacheExpiry = 0;
342
+ var sessionKeysCache = null;
343
+ var sessionKeysCacheExpiry = 0;
335
344
  async function fetchPublicKeys(issuer) {
336
- let endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
337
- if (issuer && issuer.includes("session.firebase.google.com")) {
345
+ const isSessionCookie = issuer && issuer.includes("session.firebase.google.com");
346
+ let endpoint;
347
+ let cache;
348
+ let cacheExpiry;
349
+ if (isSessionCookie) {
338
350
  endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
351
+ cache = sessionKeysCache;
352
+ cacheExpiry = sessionKeysCacheExpiry;
353
+ } else {
354
+ endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
355
+ cache = idTokenKeysCache;
356
+ cacheExpiry = idTokenKeysCacheExpiry;
339
357
  }
340
- if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
341
- return publicKeysCache;
358
+ if (cache && Date.now() < cacheExpiry) {
359
+ return cache;
342
360
  }
343
361
  const response = await fetch(endpoint);
344
362
  if (!response.ok) {
345
363
  throw new Error(`Failed to fetch Firebase public keys from ${endpoint}`);
346
364
  }
347
- publicKeysCache = await response.json();
348
- publicKeysCacheExpiry = Date.now() + 36e5;
349
- return publicKeysCache;
365
+ const keys = await response.json();
366
+ const newExpiry = Date.now() + 36e5;
367
+ if (isSessionCookie) {
368
+ sessionKeysCache = keys;
369
+ sessionKeysCacheExpiry = newExpiry;
370
+ } else {
371
+ idTokenKeysCache = keys;
372
+ idTokenKeysCacheExpiry = newExpiry;
373
+ }
374
+ return keys;
350
375
  }
351
376
  function base64UrlDecode(str) {
352
377
  let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
@@ -421,8 +446,14 @@ async function verifyIdToken(idToken) {
421
446
  let publicKeys = await fetchPublicKeys(payload.iss);
422
447
  let publicKeyPem = publicKeys[header.kid];
423
448
  if (!publicKeyPem) {
424
- publicKeysCache = null;
425
- publicKeysCacheExpiry = 0;
449
+ const isSessionCookie = payload.iss && payload.iss.includes("session.firebase.google.com");
450
+ if (isSessionCookie) {
451
+ sessionKeysCache = null;
452
+ sessionKeysCacheExpiry = 0;
453
+ } else {
454
+ idTokenKeysCache = null;
455
+ idTokenKeysCacheExpiry = 0;
456
+ }
426
457
  publicKeys = await fetchPublicKeys(payload.iss);
427
458
  publicKeyPem = publicKeys[header.kid];
428
459
  if (!publicKeyPem) {
@@ -692,6 +723,233 @@ function getAuth() {
692
723
  };
693
724
  }
694
725
 
726
+ // src/user-management.ts
727
+ init_token_generation();
728
+ init_service_account();
729
+ var IDENTITY_TOOLKIT_API = "https://identitytoolkit.googleapis.com/v1";
730
+ function convertToUserRecord(user) {
731
+ return {
732
+ uid: user.localId,
733
+ email: user.email || void 0,
734
+ emailVerified: user.emailVerified || false,
735
+ displayName: user.displayName || void 0,
736
+ photoURL: user.photoUrl || void 0,
737
+ phoneNumber: user.phoneNumber || void 0,
738
+ disabled: user.disabled || false,
739
+ metadata: {
740
+ creationTime: user.createdAt ? new Date(parseInt(user.createdAt)).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
741
+ lastSignInTime: user.lastLoginAt ? new Date(parseInt(user.lastLoginAt)).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
742
+ },
743
+ providerData: user.providerUserInfo || [],
744
+ customClaims: user.customAttributes ? JSON.parse(user.customAttributes) : void 0
745
+ };
746
+ }
747
+ async function getUserByEmail(email) {
748
+ if (!email || typeof email !== "string") {
749
+ throw new Error("email must be a non-empty string");
750
+ }
751
+ const projectId = getProjectId();
752
+ const accessToken = await getAdminAccessToken();
753
+ const response = await fetch(
754
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:lookup`,
755
+ {
756
+ method: "POST",
757
+ headers: {
758
+ "Authorization": `Bearer ${accessToken}`,
759
+ "Content-Type": "application/json"
760
+ },
761
+ body: JSON.stringify({ email: [email] })
762
+ }
763
+ );
764
+ if (!response.ok) {
765
+ const errorText = await response.text();
766
+ throw new Error(`Failed to get user by email: ${response.status} ${errorText}`);
767
+ }
768
+ const data = await response.json();
769
+ if (!data.users || data.users.length === 0) {
770
+ return null;
771
+ }
772
+ return convertToUserRecord(data.users[0]);
773
+ }
774
+ async function getUserByUid(uid) {
775
+ if (!uid || typeof uid !== "string") {
776
+ throw new Error("uid must be a non-empty string");
777
+ }
778
+ const projectId = getProjectId();
779
+ const accessToken = await getAdminAccessToken();
780
+ const response = await fetch(
781
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:lookup`,
782
+ {
783
+ method: "POST",
784
+ headers: {
785
+ "Authorization": `Bearer ${accessToken}`,
786
+ "Content-Type": "application/json"
787
+ },
788
+ body: JSON.stringify({ localId: [uid] })
789
+ }
790
+ );
791
+ if (!response.ok) {
792
+ const errorText = await response.text();
793
+ throw new Error(`Failed to get user by UID: ${response.status} ${errorText}`);
794
+ }
795
+ const data = await response.json();
796
+ if (!data.users || data.users.length === 0) {
797
+ return null;
798
+ }
799
+ return convertToUserRecord(data.users[0]);
800
+ }
801
+ async function createUser(properties) {
802
+ if (!properties || typeof properties !== "object") {
803
+ throw new Error("properties must be an object");
804
+ }
805
+ const projectId = getProjectId();
806
+ const accessToken = await getAdminAccessToken();
807
+ const requestBody = {};
808
+ if (properties.email) requestBody.email = properties.email;
809
+ if (properties.password) requestBody.password = properties.password;
810
+ if (properties.displayName) requestBody.displayName = properties.displayName;
811
+ if (properties.photoURL) requestBody.photoUrl = properties.photoURL;
812
+ if (properties.phoneNumber) requestBody.phoneNumber = properties.phoneNumber;
813
+ if (typeof properties.emailVerified === "boolean") requestBody.emailVerified = properties.emailVerified;
814
+ if (typeof properties.disabled === "boolean") requestBody.disabled = properties.disabled;
815
+ const response = await fetch(
816
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts`,
817
+ {
818
+ method: "POST",
819
+ headers: {
820
+ "Authorization": `Bearer ${accessToken}`,
821
+ "Content-Type": "application/json"
822
+ },
823
+ body: JSON.stringify(requestBody)
824
+ }
825
+ );
826
+ if (!response.ok) {
827
+ const errorText = await response.text();
828
+ throw new Error(`Failed to create user: ${response.status} ${errorText}`);
829
+ }
830
+ const data = await response.json();
831
+ return getUserByUid(data.localId);
832
+ }
833
+ async function updateUser(uid, properties) {
834
+ if (!uid || typeof uid !== "string") {
835
+ throw new Error("uid must be a non-empty string");
836
+ }
837
+ if (!properties || typeof properties !== "object") {
838
+ throw new Error("properties must be an object");
839
+ }
840
+ const projectId = getProjectId();
841
+ const accessToken = await getAdminAccessToken();
842
+ const requestBody = { localId: uid };
843
+ if (properties.email) requestBody.email = properties.email;
844
+ if (properties.password) requestBody.password = properties.password;
845
+ if (properties.displayName !== void 0) requestBody.displayName = properties.displayName;
846
+ if (properties.photoURL !== void 0) requestBody.photoUrl = properties.photoURL;
847
+ if (properties.phoneNumber !== void 0) requestBody.phoneNumber = properties.phoneNumber;
848
+ if (typeof properties.emailVerified === "boolean") requestBody.emailVerified = properties.emailVerified;
849
+ if (typeof properties.disabled === "boolean") requestBody.disableUser = properties.disabled;
850
+ const response = await fetch(
851
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:update`,
852
+ {
853
+ method: "POST",
854
+ headers: {
855
+ "Authorization": `Bearer ${accessToken}`,
856
+ "Content-Type": "application/json"
857
+ },
858
+ body: JSON.stringify(requestBody)
859
+ }
860
+ );
861
+ if (!response.ok) {
862
+ const errorText = await response.text();
863
+ throw new Error(`Failed to update user: ${response.status} ${errorText}`);
864
+ }
865
+ return getUserByUid(uid);
866
+ }
867
+ async function deleteUser(uid) {
868
+ if (!uid || typeof uid !== "string") {
869
+ throw new Error("uid must be a non-empty string");
870
+ }
871
+ const projectId = getProjectId();
872
+ const accessToken = await getAdminAccessToken();
873
+ const response = await fetch(
874
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:delete`,
875
+ {
876
+ method: "POST",
877
+ headers: {
878
+ "Authorization": `Bearer ${accessToken}`,
879
+ "Content-Type": "application/json"
880
+ },
881
+ body: JSON.stringify({ localId: uid })
882
+ }
883
+ );
884
+ if (!response.ok) {
885
+ const errorText = await response.text();
886
+ throw new Error(`Failed to delete user: ${response.status} ${errorText}`);
887
+ }
888
+ }
889
+ async function listUsers(maxResults = 1e3, pageToken) {
890
+ if (typeof maxResults !== "number" || maxResults < 1 || maxResults > 1e3) {
891
+ throw new Error("maxResults must be a number between 1 and 1000");
892
+ }
893
+ const projectId = getProjectId();
894
+ const accessToken = await getAdminAccessToken();
895
+ const params = new URLSearchParams({
896
+ maxResults: maxResults.toString()
897
+ });
898
+ if (pageToken) {
899
+ params.append("nextPageToken", pageToken);
900
+ }
901
+ const response = await fetch(
902
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:query?${params.toString()}`,
903
+ {
904
+ method: "GET",
905
+ headers: {
906
+ "Authorization": `Bearer ${accessToken}`
907
+ }
908
+ }
909
+ );
910
+ if (!response.ok) {
911
+ const errorText = await response.text();
912
+ throw new Error(`Failed to list users: ${response.status} ${errorText}`);
913
+ }
914
+ const data = await response.json();
915
+ return {
916
+ users: (data.users || []).map(convertToUserRecord),
917
+ pageToken: data.nextPageToken
918
+ };
919
+ }
920
+ async function setCustomUserClaims(uid, customClaims) {
921
+ if (!uid || typeof uid !== "string") {
922
+ throw new Error("uid must be a non-empty string");
923
+ }
924
+ if (customClaims !== null) {
925
+ const serialized = JSON.stringify(customClaims);
926
+ if (new TextEncoder().encode(serialized).length > 1e3) {
927
+ throw new Error("customClaims must be less than 1000 bytes when serialized");
928
+ }
929
+ }
930
+ const projectId = getProjectId();
931
+ const accessToken = await getAdminAccessToken();
932
+ const requestBody = {
933
+ localId: uid,
934
+ customAttributes: customClaims ? JSON.stringify(customClaims) : "{}"
935
+ };
936
+ const response = await fetch(
937
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:update`,
938
+ {
939
+ method: "POST",
940
+ headers: {
941
+ "Authorization": `Bearer ${accessToken}`,
942
+ "Content-Type": "application/json"
943
+ },
944
+ body: JSON.stringify(requestBody)
945
+ }
946
+ );
947
+ if (!response.ok) {
948
+ const errorText = await response.text();
949
+ throw new Error(`Failed to set custom claims: ${response.status} ${errorText}`);
950
+ }
951
+ }
952
+
695
953
  // src/field-value.ts
696
954
  function serverTimestamp() {
697
955
  return {
@@ -1872,8 +2130,10 @@ init_service_account();
1872
2130
  countDocuments,
1873
2131
  createCustomToken,
1874
2132
  createSessionCookie,
2133
+ createUser,
1875
2134
  deleteDocument,
1876
2135
  deleteFile,
2136
+ deleteUser,
1877
2137
  downloadFile,
1878
2138
  fileExists,
1879
2139
  generateSignedUrl,
@@ -1884,15 +2144,20 @@ init_service_account();
1884
2144
  getFileMetadata,
1885
2145
  getProjectId,
1886
2146
  getServiceAccount,
2147
+ getUserByEmail,
2148
+ getUserByUid,
1887
2149
  getUserFromToken,
1888
2150
  initializeApp,
1889
2151
  iterateCollection,
1890
2152
  listDocuments,
1891
2153
  listFiles,
2154
+ listUsers,
1892
2155
  queryDocuments,
2156
+ setCustomUserClaims,
1893
2157
  setDocument,
1894
2158
  signInWithCustomToken,
1895
2159
  updateDocument,
2160
+ updateUser,
1896
2161
  uploadFile,
1897
2162
  uploadFileResumable,
1898
2163
  verifyIdToken,
package/dist/index.mjs CHANGED
@@ -98,23 +98,41 @@ async function importPublicKeyFromX509(pem) {
98
98
  }
99
99
 
100
100
  // src/auth.ts
101
- var publicKeysCache = null;
102
- var publicKeysCacheExpiry = 0;
101
+ var idTokenKeysCache = null;
102
+ var idTokenKeysCacheExpiry = 0;
103
+ var sessionKeysCache = null;
104
+ var sessionKeysCacheExpiry = 0;
103
105
  async function fetchPublicKeys(issuer) {
104
- let endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
105
- if (issuer && issuer.includes("session.firebase.google.com")) {
106
+ const isSessionCookie = issuer && issuer.includes("session.firebase.google.com");
107
+ let endpoint;
108
+ let cache;
109
+ let cacheExpiry;
110
+ if (isSessionCookie) {
106
111
  endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
112
+ cache = sessionKeysCache;
113
+ cacheExpiry = sessionKeysCacheExpiry;
114
+ } else {
115
+ endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
116
+ cache = idTokenKeysCache;
117
+ cacheExpiry = idTokenKeysCacheExpiry;
107
118
  }
108
- if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
109
- return publicKeysCache;
119
+ if (cache && Date.now() < cacheExpiry) {
120
+ return cache;
110
121
  }
111
122
  const response = await fetch(endpoint);
112
123
  if (!response.ok) {
113
124
  throw new Error(`Failed to fetch Firebase public keys from ${endpoint}`);
114
125
  }
115
- publicKeysCache = await response.json();
116
- publicKeysCacheExpiry = Date.now() + 36e5;
117
- return publicKeysCache;
126
+ const keys = await response.json();
127
+ const newExpiry = Date.now() + 36e5;
128
+ if (isSessionCookie) {
129
+ sessionKeysCache = keys;
130
+ sessionKeysCacheExpiry = newExpiry;
131
+ } else {
132
+ idTokenKeysCache = keys;
133
+ idTokenKeysCacheExpiry = newExpiry;
134
+ }
135
+ return keys;
118
136
  }
119
137
  function base64UrlDecode(str) {
120
138
  let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
@@ -189,8 +207,14 @@ async function verifyIdToken(idToken) {
189
207
  let publicKeys = await fetchPublicKeys(payload.iss);
190
208
  let publicKeyPem = publicKeys[header.kid];
191
209
  if (!publicKeyPem) {
192
- publicKeysCache = null;
193
- publicKeysCacheExpiry = 0;
210
+ const isSessionCookie = payload.iss && payload.iss.includes("session.firebase.google.com");
211
+ if (isSessionCookie) {
212
+ sessionKeysCache = null;
213
+ sessionKeysCacheExpiry = 0;
214
+ } else {
215
+ idTokenKeysCache = null;
216
+ idTokenKeysCacheExpiry = 0;
217
+ }
194
218
  publicKeys = await fetchPublicKeys(payload.iss);
195
219
  publicKeyPem = publicKeys[header.kid];
196
220
  if (!publicKeyPem) {
@@ -460,6 +484,231 @@ function getAuth() {
460
484
  };
461
485
  }
462
486
 
487
+ // src/user-management.ts
488
+ var IDENTITY_TOOLKIT_API = "https://identitytoolkit.googleapis.com/v1";
489
+ function convertToUserRecord(user) {
490
+ return {
491
+ uid: user.localId,
492
+ email: user.email || void 0,
493
+ emailVerified: user.emailVerified || false,
494
+ displayName: user.displayName || void 0,
495
+ photoURL: user.photoUrl || void 0,
496
+ phoneNumber: user.phoneNumber || void 0,
497
+ disabled: user.disabled || false,
498
+ metadata: {
499
+ creationTime: user.createdAt ? new Date(parseInt(user.createdAt)).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
500
+ lastSignInTime: user.lastLoginAt ? new Date(parseInt(user.lastLoginAt)).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
501
+ },
502
+ providerData: user.providerUserInfo || [],
503
+ customClaims: user.customAttributes ? JSON.parse(user.customAttributes) : void 0
504
+ };
505
+ }
506
+ async function getUserByEmail(email) {
507
+ if (!email || typeof email !== "string") {
508
+ throw new Error("email must be a non-empty string");
509
+ }
510
+ const projectId = getProjectId();
511
+ const accessToken = await getAdminAccessToken();
512
+ const response = await fetch(
513
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:lookup`,
514
+ {
515
+ method: "POST",
516
+ headers: {
517
+ "Authorization": `Bearer ${accessToken}`,
518
+ "Content-Type": "application/json"
519
+ },
520
+ body: JSON.stringify({ email: [email] })
521
+ }
522
+ );
523
+ if (!response.ok) {
524
+ const errorText = await response.text();
525
+ throw new Error(`Failed to get user by email: ${response.status} ${errorText}`);
526
+ }
527
+ const data = await response.json();
528
+ if (!data.users || data.users.length === 0) {
529
+ return null;
530
+ }
531
+ return convertToUserRecord(data.users[0]);
532
+ }
533
+ async function getUserByUid(uid) {
534
+ if (!uid || typeof uid !== "string") {
535
+ throw new Error("uid must be a non-empty string");
536
+ }
537
+ const projectId = getProjectId();
538
+ const accessToken = await getAdminAccessToken();
539
+ const response = await fetch(
540
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:lookup`,
541
+ {
542
+ method: "POST",
543
+ headers: {
544
+ "Authorization": `Bearer ${accessToken}`,
545
+ "Content-Type": "application/json"
546
+ },
547
+ body: JSON.stringify({ localId: [uid] })
548
+ }
549
+ );
550
+ if (!response.ok) {
551
+ const errorText = await response.text();
552
+ throw new Error(`Failed to get user by UID: ${response.status} ${errorText}`);
553
+ }
554
+ const data = await response.json();
555
+ if (!data.users || data.users.length === 0) {
556
+ return null;
557
+ }
558
+ return convertToUserRecord(data.users[0]);
559
+ }
560
+ async function createUser(properties) {
561
+ if (!properties || typeof properties !== "object") {
562
+ throw new Error("properties must be an object");
563
+ }
564
+ const projectId = getProjectId();
565
+ const accessToken = await getAdminAccessToken();
566
+ const requestBody = {};
567
+ if (properties.email) requestBody.email = properties.email;
568
+ if (properties.password) requestBody.password = properties.password;
569
+ if (properties.displayName) requestBody.displayName = properties.displayName;
570
+ if (properties.photoURL) requestBody.photoUrl = properties.photoURL;
571
+ if (properties.phoneNumber) requestBody.phoneNumber = properties.phoneNumber;
572
+ if (typeof properties.emailVerified === "boolean") requestBody.emailVerified = properties.emailVerified;
573
+ if (typeof properties.disabled === "boolean") requestBody.disabled = properties.disabled;
574
+ const response = await fetch(
575
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts`,
576
+ {
577
+ method: "POST",
578
+ headers: {
579
+ "Authorization": `Bearer ${accessToken}`,
580
+ "Content-Type": "application/json"
581
+ },
582
+ body: JSON.stringify(requestBody)
583
+ }
584
+ );
585
+ if (!response.ok) {
586
+ const errorText = await response.text();
587
+ throw new Error(`Failed to create user: ${response.status} ${errorText}`);
588
+ }
589
+ const data = await response.json();
590
+ return getUserByUid(data.localId);
591
+ }
592
+ async function updateUser(uid, properties) {
593
+ if (!uid || typeof uid !== "string") {
594
+ throw new Error("uid must be a non-empty string");
595
+ }
596
+ if (!properties || typeof properties !== "object") {
597
+ throw new Error("properties must be an object");
598
+ }
599
+ const projectId = getProjectId();
600
+ const accessToken = await getAdminAccessToken();
601
+ const requestBody = { localId: uid };
602
+ if (properties.email) requestBody.email = properties.email;
603
+ if (properties.password) requestBody.password = properties.password;
604
+ if (properties.displayName !== void 0) requestBody.displayName = properties.displayName;
605
+ if (properties.photoURL !== void 0) requestBody.photoUrl = properties.photoURL;
606
+ if (properties.phoneNumber !== void 0) requestBody.phoneNumber = properties.phoneNumber;
607
+ if (typeof properties.emailVerified === "boolean") requestBody.emailVerified = properties.emailVerified;
608
+ if (typeof properties.disabled === "boolean") requestBody.disableUser = properties.disabled;
609
+ const response = await fetch(
610
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:update`,
611
+ {
612
+ method: "POST",
613
+ headers: {
614
+ "Authorization": `Bearer ${accessToken}`,
615
+ "Content-Type": "application/json"
616
+ },
617
+ body: JSON.stringify(requestBody)
618
+ }
619
+ );
620
+ if (!response.ok) {
621
+ const errorText = await response.text();
622
+ throw new Error(`Failed to update user: ${response.status} ${errorText}`);
623
+ }
624
+ return getUserByUid(uid);
625
+ }
626
+ async function deleteUser(uid) {
627
+ if (!uid || typeof uid !== "string") {
628
+ throw new Error("uid must be a non-empty string");
629
+ }
630
+ const projectId = getProjectId();
631
+ const accessToken = await getAdminAccessToken();
632
+ const response = await fetch(
633
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:delete`,
634
+ {
635
+ method: "POST",
636
+ headers: {
637
+ "Authorization": `Bearer ${accessToken}`,
638
+ "Content-Type": "application/json"
639
+ },
640
+ body: JSON.stringify({ localId: uid })
641
+ }
642
+ );
643
+ if (!response.ok) {
644
+ const errorText = await response.text();
645
+ throw new Error(`Failed to delete user: ${response.status} ${errorText}`);
646
+ }
647
+ }
648
+ async function listUsers(maxResults = 1e3, pageToken) {
649
+ if (typeof maxResults !== "number" || maxResults < 1 || maxResults > 1e3) {
650
+ throw new Error("maxResults must be a number between 1 and 1000");
651
+ }
652
+ const projectId = getProjectId();
653
+ const accessToken = await getAdminAccessToken();
654
+ const params = new URLSearchParams({
655
+ maxResults: maxResults.toString()
656
+ });
657
+ if (pageToken) {
658
+ params.append("nextPageToken", pageToken);
659
+ }
660
+ const response = await fetch(
661
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:query?${params.toString()}`,
662
+ {
663
+ method: "GET",
664
+ headers: {
665
+ "Authorization": `Bearer ${accessToken}`
666
+ }
667
+ }
668
+ );
669
+ if (!response.ok) {
670
+ const errorText = await response.text();
671
+ throw new Error(`Failed to list users: ${response.status} ${errorText}`);
672
+ }
673
+ const data = await response.json();
674
+ return {
675
+ users: (data.users || []).map(convertToUserRecord),
676
+ pageToken: data.nextPageToken
677
+ };
678
+ }
679
+ async function setCustomUserClaims(uid, customClaims) {
680
+ if (!uid || typeof uid !== "string") {
681
+ throw new Error("uid must be a non-empty string");
682
+ }
683
+ if (customClaims !== null) {
684
+ const serialized = JSON.stringify(customClaims);
685
+ if (new TextEncoder().encode(serialized).length > 1e3) {
686
+ throw new Error("customClaims must be less than 1000 bytes when serialized");
687
+ }
688
+ }
689
+ const projectId = getProjectId();
690
+ const accessToken = await getAdminAccessToken();
691
+ const requestBody = {
692
+ localId: uid,
693
+ customAttributes: customClaims ? JSON.stringify(customClaims) : "{}"
694
+ };
695
+ const response = await fetch(
696
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:update`,
697
+ {
698
+ method: "POST",
699
+ headers: {
700
+ "Authorization": `Bearer ${accessToken}`,
701
+ "Content-Type": "application/json"
702
+ },
703
+ body: JSON.stringify(requestBody)
704
+ }
705
+ );
706
+ if (!response.ok) {
707
+ const errorText = await response.text();
708
+ throw new Error(`Failed to set custom claims: ${response.status} ${errorText}`);
709
+ }
710
+ }
711
+
463
712
  // src/field-value.ts
464
713
  function serverTimestamp() {
465
714
  return {
@@ -1626,8 +1875,10 @@ export {
1626
1875
  countDocuments,
1627
1876
  createCustomToken,
1628
1877
  createSessionCookie,
1878
+ createUser,
1629
1879
  deleteDocument,
1630
1880
  deleteFile,
1881
+ deleteUser,
1631
1882
  downloadFile,
1632
1883
  fileExists,
1633
1884
  generateSignedUrl,
@@ -1638,15 +1889,20 @@ export {
1638
1889
  getFileMetadata,
1639
1890
  getProjectId,
1640
1891
  getServiceAccount,
1892
+ getUserByEmail,
1893
+ getUserByUid,
1641
1894
  getUserFromToken,
1642
1895
  initializeApp,
1643
1896
  iterateCollection,
1644
1897
  listDocuments,
1645
1898
  listFiles,
1899
+ listUsers,
1646
1900
  queryDocuments,
1901
+ setCustomUserClaims,
1647
1902
  setDocument,
1648
1903
  signInWithCustomToken,
1649
1904
  updateDocument,
1905
+ updateUser,
1650
1906
  uploadFile,
1651
1907
  uploadFileResumable,
1652
1908
  verifyIdToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -26,8 +26,8 @@
26
26
  "typecheck": "tsc --noEmit",
27
27
  "test": "jest",
28
28
  "test:watch": "jest --watch",
29
- "test:e2e": "jest --config jest.e2e.config.js",
30
- "test:e2e:watch": "jest --config jest.e2e.config.js --watch",
29
+ "test:e2e": "node --env-file=.env node_modules/.bin/jest --config jest.e2e.config.js",
30
+ "test:e2e:watch": "node --env-file=.env node_modules/.bin/jest --config jest.e2e.config.js --watch",
31
31
  "test:all": "npm test && npm run test:e2e",
32
32
  "prepublishOnly": "npm run build"
33
33
  },