cloudfire-auth 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +13 -0
  3. package/README.md +94 -0
  4. package/dist/CloudFireAuth.d.ts +291 -0
  5. package/dist/CloudFireAuth.js +346 -0
  6. package/dist/google-auth/get-oauth-2-token.d.ts +15 -0
  7. package/dist/google-auth/get-oauth-2-token.js +66 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/rest-api/create-user.d.ts +2 -0
  11. package/dist/rest-api/create-user.js +3 -0
  12. package/dist/rest-api/delete-user.d.ts +175 -0
  13. package/dist/rest-api/delete-user.js +207 -0
  14. package/dist/rest-api/delete-users.d.ts +2 -0
  15. package/dist/rest-api/delete-users.js +3 -0
  16. package/dist/rest-api/get-user-by-email.d.ts +2 -0
  17. package/dist/rest-api/get-user-by-email.js +3 -0
  18. package/dist/rest-api/get-user-by-phone-number.d.ts +2 -0
  19. package/dist/rest-api/get-user-by-phone-number.js +3 -0
  20. package/dist/rest-api/get-user-by-provider-uid.d.ts +2 -0
  21. package/dist/rest-api/get-user-by-provider-uid.js +3 -0
  22. package/dist/rest-api/get-user.d.ts +99 -0
  23. package/dist/rest-api/get-user.js +177 -0
  24. package/dist/rest-api/get-users.d.ts +2 -0
  25. package/dist/rest-api/get-users.js +3 -0
  26. package/dist/rest-api/list-users.d.ts +2 -0
  27. package/dist/rest-api/list-users.js +3 -0
  28. package/dist/rest-api/revoke-refresh-tokens.d.ts +116 -0
  29. package/dist/rest-api/revoke-refresh-tokens.js +151 -0
  30. package/dist/rest-api/set-custom-user-claims.d.ts +173 -0
  31. package/dist/rest-api/set-custom-user-claims.js +261 -0
  32. package/dist/rest-api/standard-request.d.ts +12 -0
  33. package/dist/rest-api/standard-request.js +20 -0
  34. package/dist/rest-api/update-user.d.ts +175 -0
  35. package/dist/rest-api/update-user.js +375 -0
  36. package/dist/rest-api/verify-id-token.d.ts +127 -0
  37. package/dist/rest-api/verify-id-token.js +273 -0
  38. package/dist/rest-api/verify-session-cookie.d.ts +2 -0
  39. package/dist/rest-api/verify-session-cookie.js +3 -0
  40. package/dist/types/firebase-admin/auth-config.d.ts +851 -0
  41. package/dist/types/firebase-admin/auth-config.js +1 -0
  42. package/dist/types/firebase-admin/identifier.d.ts +57 -0
  43. package/dist/types/firebase-admin/identifier.js +1 -0
  44. package/dist/types/firebase-admin/index.d.ts +153 -0
  45. package/dist/types/firebase-admin/index.js +1 -0
  46. package/dist/types/firebase-admin/token-verifier.d.ts +219 -0
  47. package/dist/types/firebase-admin/token-verifier.js +1 -0
  48. package/dist/types/firebase-admin/user-record.d.ts +289 -0
  49. package/dist/types/firebase-admin/user-record.js +1 -0
  50. package/dist/types/google-auth.d.ts +25 -0
  51. package/dist/types/google-auth.js +1 -0
  52. package/dist/types/service-account-key.d.ts +13 -0
  53. package/dist/types/service-account-key.js +1 -0
  54. package/dist/types.d.ts +6 -0
  55. package/dist/types.js +1 -0
  56. package/package.json +56 -0
  57. package/third_party/firebase-admin-license.txt +201 -0
@@ -0,0 +1,273 @@
1
+ import { decodeJwt, decodeProtectedHeader, importX509, jwtVerify } from "jose";
2
+ /**
3
+ * Verifies a Firebase ID token (JWT) and returns its decoded claims.
4
+ *
5
+ * This function performs comprehensive validation of Firebase ID tokens including:
6
+ * - JWT structure and format validation
7
+ * - Cryptographic signature verification using Google's public keys
8
+ * - Firebase-specific claim validation (audience, issuer, timing)
9
+ * - Optional revocation status checking
10
+ *
11
+ * The verification process follows Firebase's recommended security practices:
12
+ * 1. Validates JWT body claims (aud, iss, sub, exp, iat, auth_time)
13
+ * 2. Validates JWT header (algorithm RS256, key ID)
14
+ * 3. Fetches and caches Google's signing keys from their public API
15
+ * 4. Verifies cryptographic signature using the appropriate public key
16
+ * 5. Optionally checks if the user's tokens have been revoked
17
+ *
18
+ * **Key Features:**
19
+ * - Automatic public key fetching and caching (when KV namespace provided)
20
+ * - Proper error handling with descriptive error messages
21
+ * - Support for revocation checking via Firebase Admin API
22
+ * - Network error resilience
23
+ *
24
+ * **Security Validations:**
25
+ * - Ensures token audience matches the provided project ID
26
+ * - Verifies token was issued by Firebase (`https://securetoken.google.com/{projectId}`)
27
+ * - Checks token hasn't expired and wasn't issued in the future
28
+ * - Validates authentication time is not in the future
29
+ * - Ensures subject (user ID) is a non-empty string
30
+ * - Confirms token uses RS256 algorithm (not vulnerable algorithms)
31
+ *
32
+ * **Performance Optimizations:**
33
+ * - Caches Google public keys in KV storage to avoid repeated API calls
34
+ * - Respects cache headers from Google's key endpoint
35
+ * - Fails fast on basic validation errors before expensive crypto operations
36
+ *
37
+ * @param idToken - The Firebase ID token (JWT) to verify. Must be a valid JWT string.
38
+ * @param projectId - Your Firebase project ID. Used to validate token audience and issuer.
39
+ * @param oauth2Token - OAuth2 access token for Firebase Admin API. Required for revocation checks.
40
+ * @param kv - Optional Cloudflare KV namespace for caching Google's public keys.
41
+ * Improves performance by avoiding repeated key fetches.
42
+ * @param checkRevoked - Whether to check if the token has been revoked by comparing
43
+ * against the user's `tokensValidAfterTime`. Requires an additional
44
+ * API call to Firebase Admin API.
45
+ *
46
+ * @returns A Promise that resolves to the decoded ID token containing user claims
47
+ * and Firebase-specific metadata. The returned object includes standard
48
+ * JWT claims (iss, aud, exp, iat, sub) plus Firebase-specific claims
49
+ * (email, email_verified, firebase, etc.).
50
+ *
51
+ * @throws {Error} When token validation fails:
52
+ * - "Token audience does not match project ID" - Wrong project ID
53
+ * - "Token issuer does not match project ID" - Not issued by Firebase
54
+ * - "Token expiration date is in the past" - Expired token
55
+ * - "Token issued at date is in the future" - Clock skew or forged token
56
+ * - "Token subject is empty" - Missing or invalid user ID
57
+ * - "Token algorithm is not RS256" - Unsafe algorithm
58
+ * - "Token key ID is not in the Google API" - Unknown or rotated key
59
+ * - "Token is invalid" - Signature verification failed
60
+ * - "Token is revoked" - User tokens were revoked (when checkRevoked=true)
61
+ * - Network errors when fetching Google's public keys or checking revocation
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * // Basic token verification
66
+ * const decodedToken = await verifyIdTokenHandler(
67
+ * idToken,
68
+ * "my-project-id",
69
+ * oauth2Token
70
+ * );
71
+ * console.log("User ID:", decodedToken.uid);
72
+ * console.log("Email:", decodedToken.email);
73
+ *
74
+ * // With caching and revocation checking
75
+ * const decodedToken = await verifyIdTokenHandler(
76
+ * idToken,
77
+ * "my-project-id",
78
+ * oauth2Token,
79
+ * kvNamespace, // Enables key caching
80
+ * true // Check if token was revoked
81
+ * );
82
+ * ```
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * // Error handling
87
+ * try {
88
+ * const decodedToken = await verifyIdTokenHandler(idToken, projectId, oauth2Token);
89
+ * // Token is valid, proceed with authenticated request
90
+ * return processAuthenticatedRequest(decodedToken);
91
+ * } catch (error) {
92
+ * if (error.message.includes('expired')) {
93
+ * return { error: 'Please log in again' };
94
+ * } else if (error.message.includes('revoked')) {
95
+ * return { error: 'Access has been revoked' };
96
+ * } else {
97
+ * return { error: 'Invalid token' };
98
+ * }
99
+ * }
100
+ * ```
101
+ *
102
+ * @see {@link https://firebase.google.com/docs/auth/admin/verify-id-tokens Firebase Admin SDK Token Verification}
103
+ * @see {@link https://tools.ietf.org/html/rfc7519 JWT RFC 7519}
104
+ * @see {@link https://firebase.google.com/docs/reference/admin/node/firebase-admin.auth.auth.md#authverifyidtoken Firebase verifyIdToken Reference}
105
+ *
106
+ * @since 1.0.0
107
+ * @package
108
+ */
109
+ export async function verifyIdTokenHandler(idToken, projectId, oauth2Token, kv, checkRevoked) {
110
+ const { isValid: isValidBody, errorMessage: errorMessageBody } = await validateJwtBody(idToken, projectId);
111
+ if (!isValidBody) {
112
+ throw new Error(errorMessageBody);
113
+ }
114
+ const { isValid: isValidHeader, errorMessage: errorMessageHeader, signingKey } = await validateJwtHeader(idToken, kv);
115
+ if (!isValidHeader) {
116
+ throw new Error(errorMessageHeader);
117
+ }
118
+ const { isValid: tokenVerified, payload: tokenPayload } = await verifyToken(idToken, signingKey, projectId);
119
+ if (!tokenVerified) {
120
+ throw new Error("Token is invalid");
121
+ }
122
+ const localId = tokenPayload?.sub;
123
+ if (checkRevoked === true) {
124
+ const tokenValidSinceTime = await getTokenValidSinceTime(localId, oauth2Token);
125
+ if (tokenPayload?.iat && tokenValidSinceTime > tokenPayload.iat) {
126
+ throw new Error("Token is revoked");
127
+ }
128
+ }
129
+ return tokenPayload;
130
+ }
131
+ /**
132
+ * Gets the token validSince time for the user corresponding to the ID token.
133
+ * If the token was issued before this validSince time, the token is invalid.
134
+ * @param localId - The local ID of the user to get the token valid since time for.
135
+ * @param oauth2Token - The OAuth2 token for the Firebase Admin API.
136
+ * @returns The token valid since time.
137
+ */
138
+ async function getTokenValidSinceTime(localId, oauth2Token) {
139
+ const accountLookupResponse = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:lookup`, {
140
+ method: "POST",
141
+ headers: {
142
+ "Content-Type": "application/json",
143
+ Authorization: `Bearer ${oauth2Token}`,
144
+ },
145
+ body: JSON.stringify({ localId: [localId] }),
146
+ });
147
+ const accountLookupJson = (await accountLookupResponse.json());
148
+ if (!accountLookupJson.users[0]?.validSince) {
149
+ throw new Error("Token valid since time is not a string");
150
+ }
151
+ return parseInt(accountLookupJson.users[0].validSince);
152
+ }
153
+ /**
154
+ * Validates the body of a JWT. The token is expected to come from Firebase Auth,
155
+ * because of this it has specific required fields that are checked for. If these
156
+ * fields are not present, the token is invalid.
157
+ *
158
+ * @param token - The JWT to validate.
159
+ * @returns True if the token is valid, false and an error message if the token is invalid.
160
+ */
161
+ async function validateJwtBody(token, projectId) {
162
+ const firebaseJwtBody = decodeJwt(token);
163
+ if (firebaseJwtBody.aud !== projectId) {
164
+ return {
165
+ isValid: false,
166
+ errorMessage: `Token audience does not match project ID, expected ${projectId}, got ${firebaseJwtBody.aud}`,
167
+ };
168
+ }
169
+ if (typeof firebaseJwtBody.sub !== "string") {
170
+ return {
171
+ isValid: false,
172
+ errorMessage: "Token subject is not a string",
173
+ };
174
+ }
175
+ if (firebaseJwtBody.sub === "") {
176
+ return {
177
+ isValid: false,
178
+ errorMessage: `Token subject is empty`,
179
+ };
180
+ }
181
+ if (firebaseJwtBody.iss !== `https://securetoken.google.com/${projectId}`) {
182
+ return {
183
+ isValid: false,
184
+ errorMessage: `Token issuer does not match project ID, expected https://securetoken.google.com/${projectId}, got ${firebaseJwtBody.iss}`,
185
+ };
186
+ }
187
+ const currentTime = Math.ceil(Date.now() / 1000);
188
+ if (firebaseJwtBody.exp && firebaseJwtBody.exp < currentTime) {
189
+ return { isValid: false, errorMessage: "Token expiration date is in the past" };
190
+ }
191
+ if (firebaseJwtBody.iat && firebaseJwtBody.iat > currentTime) {
192
+ return { isValid: false, errorMessage: "Token issued at date is in the future" };
193
+ }
194
+ if (firebaseJwtBody.auth_time && typeof firebaseJwtBody.auth_time !== "number") {
195
+ return { isValid: false, errorMessage: "Token auth time is not a number" };
196
+ }
197
+ if (firebaseJwtBody.auth_time > currentTime) {
198
+ return { isValid: false, errorMessage: "Token auth time is in the future" };
199
+ }
200
+ return { isValid: true };
201
+ }
202
+ /**
203
+ * Validates the header of a Firebase JWT. First checks the algorithm type is
204
+ * the expected type, then checks the key ID is valid either by checking in the
205
+ * KV namespace or by fetching the key from the Google API.
206
+ *
207
+ * If a new key is fetched from the Google API that was used to sign the token
208
+ * then it is stored in the KV namespace for the duration of the cache time
209
+ * returned from the Google API in the headers.
210
+ *
211
+ * @param token - The JWT to validate.
212
+ * @param kv - The KV namespace to get the Google public keys from.
213
+ * @returns True and the signing key if the token is valid, false and an error message if the token is invalid.
214
+ */
215
+ export async function validateJwtHeader(token, kv) {
216
+ const firebaseJwtHeader = decodeProtectedHeader(token);
217
+ // console.log("firebaseJwtHeader", firebaseJwtHeader);
218
+ if (firebaseJwtHeader.alg !== "RS256") {
219
+ return { isValid: false, errorMessage: "Token algorithm is not RS256" };
220
+ }
221
+ const expectedKeyId = `googlePublicKey-${firebaseJwtHeader.kid}`;
222
+ if (kv) {
223
+ const googlePublicKey = await kv.get(expectedKeyId);
224
+ if (googlePublicKey) {
225
+ return { isValid: true, signingKey: googlePublicKey };
226
+ }
227
+ }
228
+ console.log("Token key ID is not in the KV namespace, fetching from Google API");
229
+ const googlePublicKeys = await getGooglePublicKeys();
230
+ const signingKey = googlePublicKeys.keys[firebaseJwtHeader.kid];
231
+ if (!signingKey) {
232
+ return { isValid: false, errorMessage: "Token key ID is not in the Google API" };
233
+ }
234
+ if (kv) {
235
+ const cacheDuration = googlePublicKeys.cache_duration;
236
+ if (cacheDuration > 0) {
237
+ await kv.put(expectedKeyId, signingKey, { expirationTtl: cacheDuration });
238
+ }
239
+ }
240
+ return { isValid: true, signingKey };
241
+ }
242
+ /**
243
+ * Pulls the google public keys from the Google API and extracts the cache time
244
+ * for the keys. Returns these in an object.
245
+ * @returns The Google public keys.
246
+ */
247
+ async function getGooglePublicKeys() {
248
+ const googlePublicKeysResponse = await fetch("https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com");
249
+ console.log("googlePublicKeysResponse", googlePublicKeysResponse.status);
250
+ const googlePublicKeysJson = await googlePublicKeysResponse.json();
251
+ const cacheDuration = googlePublicKeysResponse.headers.get("cache-control")?.split("=")[1];
252
+ const googlePublicKeys = {
253
+ keys: googlePublicKeysJson,
254
+ cache_duration: cacheDuration ? parseInt(cacheDuration) : 0,
255
+ };
256
+ return googlePublicKeys;
257
+ }
258
+ /**
259
+ * Verifies a Firebase ID token using Google' public keys, and their suggested
260
+ * simple validation checks. You can read about these checks here:
261
+ * https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library
262
+ * @param token - The Firebase ID token to verify
263
+ * @returns True if the token is valid, false otherwise
264
+ */
265
+ async function verifyToken(token, signingKey, projectId) {
266
+ const tokenVerificationKey = await importX509(signingKey, "RS256");
267
+ const { payload } = await jwtVerify(token, tokenVerificationKey, {
268
+ issuer: `https://securetoken.google.com/${projectId}`,
269
+ audience: projectId,
270
+ });
271
+ console.log("Token verified");
272
+ return { isValid: true, payload: payload };
273
+ }
@@ -0,0 +1,2 @@
1
+ import type { DecodedIdToken } from "../types.js";
2
+ export declare function verifySessionCookieHandler(sessionCookie: string, checkRevoked?: boolean): Promise<DecodedIdToken>;
@@ -0,0 +1,3 @@
1
+ export async function verifySessionCookieHandler(sessionCookie, checkRevoked) {
2
+ throw new Error("Not implemented");
3
+ }