@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 +117 -6
- package/CHANGELOG.md +25 -0
- package/README.md +93 -1
- package/dist/index.d.mts +125 -1
- package/dist/index.d.ts +125 -1
- package/dist/index.js +276 -11
- package/dist/index.mjs +267 -11
- package/package.json +3 -3
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.
|
|
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. **
|
|
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/
|
|
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 |
|
|
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
|
|
334
|
-
var
|
|
340
|
+
var idTokenKeysCache = null;
|
|
341
|
+
var idTokenKeysCacheExpiry = 0;
|
|
342
|
+
var sessionKeysCache = null;
|
|
343
|
+
var sessionKeysCacheExpiry = 0;
|
|
335
344
|
async function fetchPublicKeys(issuer) {
|
|
336
|
-
|
|
337
|
-
|
|
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 (
|
|
341
|
-
return
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
425
|
-
|
|
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
|
|
102
|
-
var
|
|
101
|
+
var idTokenKeysCache = null;
|
|
102
|
+
var idTokenKeysCacheExpiry = 0;
|
|
103
|
+
var sessionKeysCache = null;
|
|
104
|
+
var sessionKeysCacheExpiry = 0;
|
|
103
105
|
async function fetchPublicKeys(issuer) {
|
|
104
|
-
|
|
105
|
-
|
|
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 (
|
|
109
|
-
return
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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.
|
|
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
|
},
|