From 18a19597752424143f0bc64762ec34e85196076f Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 14 Nov 2023 10:04:18 -0800 Subject: [PATCH 01/15] adding optional param to token verifier --- src/auth/token-verifier.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 05566205b5..42c5fc5357 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -414,7 +414,7 @@ export class FirebaseTokenVerifier { public _verifyAuthBlockingToken( jwtToken: string, isEmulator: boolean, - audience: string | undefined): Promise { + audience: string | undefined, eventType?: string): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -454,10 +454,10 @@ export class FirebaseTokenVerifier { token: string, projectId: string, isEmulator: boolean, - audience?: string): Promise { + audience?: string, eventType?: string): Promise { return this.safeDecode(token) .then((decodedToken) => { - this.verifyContent(decodedToken, projectId, isEmulator, audience); + this.verifyContent(decodedToken, projectId, isEmulator, audience, eventType); return this.verifySignature(token, isEmulator) .then(() => decodedToken); }); @@ -490,7 +490,8 @@ export class FirebaseTokenVerifier { fullDecodedToken: DecodedToken, projectId: string | null, isEmulator: boolean, - audience: string | undefined): void { + audience: string | undefined, + eventType?: string): void { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; @@ -529,13 +530,15 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; - } else if (typeof payload.sub !== 'string') { - errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; - } else if (payload.sub === '') { - errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; - } else if (payload.sub.length > 128) { - errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + - verifyJwtTokenDocsMessage; + } else if (!(eventType === 'beforeEmailSent' || eventType === 'beforeSmsSent')) { + if(typeof payload.sub !== 'string') { + errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; + } else if (payload.sub === '') { + errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; + } else if (payload.sub.length > 128) { + errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + + verifyJwtTokenDocsMessage; + } } if (errorMessage) { throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); From 889db29214ce379326ec8a8ac6956e1dab8e5044 Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 14 Nov 2023 10:12:35 -0800 Subject: [PATCH 02/15] update _verifyAuthBlockingToken method --- src/auth/token-verifier.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 42c5fc5357..853c38168d 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -321,6 +321,13 @@ export interface FirebaseTokenInfo { expiredErrorCode: ErrorInfo; } +/** + * Shorthand auth blocking events from GCIP. + * @hidden + * @alpha + */ +export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail" | "beforeSendSms"; + /** * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies. * @@ -414,7 +421,8 @@ export class FirebaseTokenVerifier { public _verifyAuthBlockingToken( jwtToken: string, isEmulator: boolean, - audience: string | undefined, eventType?: string): Promise { + audience: string | undefined, + eventType?: AuthBlockingEventType): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -427,7 +435,7 @@ export class FirebaseTokenVerifier { if (typeof audience === 'undefined') { audience = `${projectId}.cloudfunctions.net/`; } - return this.decodeAndVerify(jwtToken, projectId, isEmulator, audience); + return this.decodeAndVerify(jwtToken, projectId, isEmulator, audience, eventType); }) .then((decoded) => { const decodedAuthBlockingToken = decoded.payload as DecodedAuthBlockingToken; @@ -454,7 +462,7 @@ export class FirebaseTokenVerifier { token: string, projectId: string, isEmulator: boolean, - audience?: string, eventType?: string): Promise { + audience?: string, eventType?: AuthBlockingEventType): Promise { return this.safeDecode(token) .then((decodedToken) => { this.verifyContent(decodedToken, projectId, isEmulator, audience, eventType); @@ -491,7 +499,7 @@ export class FirebaseTokenVerifier { projectId: string | null, isEmulator: boolean, audience: string | undefined, - eventType?: string): void { + eventType?: AuthBlockingEventType): void { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; @@ -530,7 +538,7 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; - } else if (!(eventType === 'beforeEmailSent' || eventType === 'beforeSmsSent')) { + } else if (!(eventType === 'beforeSendSms' || eventType === 'beforeSendEmail')) { if(typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { From ed48636d94e0df33d28da81fd34c006790046b70 Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 14 Nov 2023 10:17:54 -0800 Subject: [PATCH 03/15] fix lint --- src/auth/token-verifier.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 853c38168d..138739e272 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -326,7 +326,7 @@ export interface FirebaseTokenInfo { * @hidden * @alpha */ -export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail" | "beforeSendSms"; +export type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn' | 'beforeSendEmail' | 'beforeSendSms'; /** * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies. @@ -539,10 +539,11 @@ export class FirebaseTokenVerifier { `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; } else if (!(eventType === 'beforeSendSms' || eventType === 'beforeSendEmail')) { - if(typeof payload.sub !== 'string') { + if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { - errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; + errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + + verifyJwtTokenDocsMessage; } else if (payload.sub.length > 128) { errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + verifyJwtTokenDocsMessage; From 43f6df5017d6e701b9c109342232d076a8ef0827 Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 14 Nov 2023 10:26:25 -0800 Subject: [PATCH 04/15] adding public definitions --- etc/firebase-admin.auth.api.md | 4 +++- src/auth/base-auth.ts | 6 ++++-- src/auth/token-verifier.ts | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 3723abd051..22a006c357 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -94,8 +94,10 @@ export abstract class BaseAuth { setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; updateUser(uid: string, properties: UpdateRequest): Promise; + // Warning: (ae-forgotten-export) The symbol "AuthBlockingEventType" needs to be exported by the entry point index.d.ts + // // @alpha (undocumented) - _verifyAuthBlockingToken(token: string, audience?: string): Promise; + _verifyAuthBlockingToken(token: string, audience?: string, eventType?: AuthBlockingEventType): Promise; verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index 6f77e088f8..b479ed0e6d 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -28,6 +28,7 @@ import { createAuthBlockingTokenVerifier, DecodedIdToken, DecodedAuthBlockingToken, + AuthBlockingEventType, } from './token-verifier'; import { AuthProviderConfig, SAMLAuthProviderConfig, AuthProviderConfigFilter, ListProviderConfigResults, @@ -1095,10 +1096,11 @@ export abstract class BaseAuth { // eslint-disable-next-line @typescript-eslint/naming-convention public _verifyAuthBlockingToken( token: string, - audience?: string + audience?: string, + eventType?: AuthBlockingEventType, ): Promise { const isEmulator = useEmulator(); - return this.authBlockingTokenVerifier._verifyAuthBlockingToken(token, isEmulator, audience) + return this.authBlockingTokenVerifier._verifyAuthBlockingToken(token, isEmulator, audience, eventType) .then((decodedAuthBlockingToken: DecodedAuthBlockingToken) => { return decodedAuthBlockingToken; }); diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 138739e272..c68b94188e 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -323,8 +323,7 @@ export interface FirebaseTokenInfo { /** * Shorthand auth blocking events from GCIP. - * @hidden - * @alpha + * */ export type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn' | 'beforeSendEmail' | 'beforeSendSms'; From 4ed2e1a7453bec5402370d66e6b219048c936994 Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 14 Nov 2023 10:27:59 -0800 Subject: [PATCH 05/15] add AuthBlockingEventType type --- etc/firebase-admin.auth.api.md | 5 +++-- src/auth/index.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 22a006c357..0f0629cfb5 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -55,6 +55,9 @@ export class Auth extends BaseAuth { tenantManager(): TenantManager; } +// @public +export type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn' | 'beforeSendEmail' | 'beforeSendSms'; + // @public export type AuthFactorType = 'phone'; @@ -94,8 +97,6 @@ export abstract class BaseAuth { setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; updateUser(uid: string, properties: UpdateRequest): Promise; - // Warning: (ae-forgotten-export) The symbol "AuthBlockingEventType" needs to be exported by the entry point index.d.ts - // // @alpha (undocumented) _verifyAuthBlockingToken(token: string, audience?: string, eventType?: AuthBlockingEventType): Promise; verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; diff --git a/src/auth/index.ts b/src/auth/index.ts index a559a706f8..0fd4d15b4b 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -144,7 +144,8 @@ export { export { DecodedIdToken, - DecodedAuthBlockingToken + DecodedAuthBlockingToken, + AuthBlockingEventType } from './token-verifier'; export { From 546649b4ebdefc5049c642a83b9a98ac0c614ca0 Mon Sep 17 00:00:00 2001 From: Pragati Date: Wed, 15 Nov 2023 13:42:10 -0800 Subject: [PATCH 06/15] use decoded payload instead --- etc/firebase-admin.auth.api.md | 5 +---- src/auth/base-auth.ts | 6 ++---- src/auth/index.ts | 3 +-- src/auth/token-verifier.ts | 22 +++++++--------------- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 0f0629cfb5..3723abd051 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -55,9 +55,6 @@ export class Auth extends BaseAuth { tenantManager(): TenantManager; } -// @public -export type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn' | 'beforeSendEmail' | 'beforeSendSms'; - // @public export type AuthFactorType = 'phone'; @@ -98,7 +95,7 @@ export abstract class BaseAuth { updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; updateUser(uid: string, properties: UpdateRequest): Promise; // @alpha (undocumented) - _verifyAuthBlockingToken(token: string, audience?: string, eventType?: AuthBlockingEventType): Promise; + _verifyAuthBlockingToken(token: string, audience?: string): Promise; verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index b479ed0e6d..6f77e088f8 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -28,7 +28,6 @@ import { createAuthBlockingTokenVerifier, DecodedIdToken, DecodedAuthBlockingToken, - AuthBlockingEventType, } from './token-verifier'; import { AuthProviderConfig, SAMLAuthProviderConfig, AuthProviderConfigFilter, ListProviderConfigResults, @@ -1096,11 +1095,10 @@ export abstract class BaseAuth { // eslint-disable-next-line @typescript-eslint/naming-convention public _verifyAuthBlockingToken( token: string, - audience?: string, - eventType?: AuthBlockingEventType, + audience?: string ): Promise { const isEmulator = useEmulator(); - return this.authBlockingTokenVerifier._verifyAuthBlockingToken(token, isEmulator, audience, eventType) + return this.authBlockingTokenVerifier._verifyAuthBlockingToken(token, isEmulator, audience) .then((decodedAuthBlockingToken: DecodedAuthBlockingToken) => { return decodedAuthBlockingToken; }); diff --git a/src/auth/index.ts b/src/auth/index.ts index 0fd4d15b4b..a559a706f8 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -144,8 +144,7 @@ export { export { DecodedIdToken, - DecodedAuthBlockingToken, - AuthBlockingEventType + DecodedAuthBlockingToken } from './token-verifier'; export { diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index c68b94188e..1481ebdf0b 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -321,12 +321,6 @@ export interface FirebaseTokenInfo { expiredErrorCode: ErrorInfo; } -/** - * Shorthand auth blocking events from GCIP. - * - */ -export type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn' | 'beforeSendEmail' | 'beforeSendSms'; - /** * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies. * @@ -420,8 +414,7 @@ export class FirebaseTokenVerifier { public _verifyAuthBlockingToken( jwtToken: string, isEmulator: boolean, - audience: string | undefined, - eventType?: AuthBlockingEventType): Promise { + audience: string | undefined): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -434,7 +427,7 @@ export class FirebaseTokenVerifier { if (typeof audience === 'undefined') { audience = `${projectId}.cloudfunctions.net/`; } - return this.decodeAndVerify(jwtToken, projectId, isEmulator, audience, eventType); + return this.decodeAndVerify(jwtToken, projectId, isEmulator, audience); }) .then((decoded) => { const decodedAuthBlockingToken = decoded.payload as DecodedAuthBlockingToken; @@ -461,10 +454,10 @@ export class FirebaseTokenVerifier { token: string, projectId: string, isEmulator: boolean, - audience?: string, eventType?: AuthBlockingEventType): Promise { + audience?: string): Promise { return this.safeDecode(token) .then((decodedToken) => { - this.verifyContent(decodedToken, projectId, isEmulator, audience, eventType); + this.verifyContent(decodedToken, projectId, isEmulator, audience); return this.verifySignature(token, isEmulator) .then(() => decodedToken); }); @@ -497,11 +490,10 @@ export class FirebaseTokenVerifier { fullDecodedToken: DecodedToken, projectId: string | null, isEmulator: boolean, - audience: string | undefined, - eventType?: AuthBlockingEventType): void { + audience: string | undefined): void { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; - + console.log("PAYLOAD= ", JSON.stringify(payload)); const projectIdMatchMessage = ` Make sure the ${this.tokenInfo.shortName} comes from the same ` + 'Firebase project as the service account used to authenticate this SDK.'; const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + @@ -537,7 +529,7 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; - } else if (!(eventType === 'beforeSendSms' || eventType === 'beforeSendEmail')) { + } else if (!(payload.event_type !== undefined && (payload.event_type === 'beforeSendSms' || payload.event_type === 'beforeSendEmail'))) { if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { From 887a9565de0bec48f461d4eff2bac20d82876fba Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 11 Jan 2024 00:30:38 +0530 Subject: [PATCH 07/15] remove debug statement --- src/auth/token-verifier.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 1481ebdf0b..5a252bca11 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -493,7 +493,7 @@ export class FirebaseTokenVerifier { audience: string | undefined): void { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; - console.log("PAYLOAD= ", JSON.stringify(payload)); + const projectIdMatchMessage = ` Make sure the ${this.tokenInfo.shortName} comes from the same ` + 'Firebase project as the service account used to authenticate this SDK.'; const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + @@ -533,7 +533,7 @@ export class FirebaseTokenVerifier { if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { - errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + + errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub.length > 128) { errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + From 0cf726ef0a002b2f3710935f71ffcf2cbba2c902 Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 11 Jan 2024 00:36:25 +0530 Subject: [PATCH 08/15] formatting --- src/auth/token-verifier.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 5a252bca11..79d694f73f 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -529,7 +529,8 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; - } else if (!(payload.event_type !== undefined && (payload.event_type === 'beforeSendSms' || payload.event_type === 'beforeSendEmail'))) { + } else if (!(payload.event_type !== undefined && + (payload.event_type === 'beforeSendSms' || payload.event_type === 'beforeSendEmail'))) { if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { From 71737885231499dcce21f81693378638f4dca36b Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 18 Mar 2024 09:15:04 -0700 Subject: [PATCH 09/15] add unit tests --- test/unit/auth/token-verifier.spec.ts | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 7abda8eb08..b1dd34b1f8 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -780,5 +780,43 @@ describe('FirebaseTokenVerifier', () => { await authBlockingTokenVerifier._verifyAuthBlockingToken(idTokenNoHeader, false, undefined) .should.eventually.be.rejectedWith('Firebase Auth Blocking token has no "kid" claim.'); }); + + it('should not throw an error for valid event_type in Auth Blocking JWT token', () => { + const authBlockingTokenWithValidEventType = mocks.generateAuthBlockingToken({ + payload: { + event_type: 'beforeSendSms', + }, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithValidEventType, false, undefined) + .should.eventually.be.fulfilled; + }); + + it('should not throw an error for another valid event_type in Auth Blocking JWT token', () => { + const authBlockingTokenWithValidEventType = mocks.generateAuthBlockingToken({ + payload: { + event_type: 'beforeSendEmail', + }, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithValidEventType, false, undefined) + .should.eventually.be.fulfilled; + }); + + it('should throw an error for invalid event_type in Auth Blocking JWT token', () => { + const authBlockingTokenWithInvalidEventType = mocks.generateAuthBlockingToken({ + payload: { + event_type: 'invalidEventType', + }, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithInvalidEventType, false, undefined) + .should.eventually.be.rejected; + }); + + it('should throw an error when event_type is missing in Auth Blocking JWT token', () => { + const authBlockingTokenWithNoEventType = mocks.generateAuthBlockingToken({ + payload: {}, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithNoEventType, false, undefined) + .should.eventually.be.rejected; + }); }); }); From f1a5246a8f4229f45507b91c668b5c67d18f146a Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 18 Mar 2024 12:23:21 -0700 Subject: [PATCH 10/15] unit test --- test/unit/auth/token-verifier.spec.ts | 44 ++++++--------------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index b1dd34b1f8..998ed914cf 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -781,42 +781,16 @@ describe('FirebaseTokenVerifier', () => { .should.eventually.be.rejectedWith('Firebase Auth Blocking token has no "kid" claim.'); }); - it('should not throw an error for valid event_type in Auth Blocking JWT token', () => { - const authBlockingTokenWithValidEventType = mocks.generateAuthBlockingToken({ - payload: { - event_type: 'beforeSendSms', - }, - }); - return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithValidEventType, false, undefined) - .should.eventually.be.fulfilled; - }); - - it('should not throw an error for another valid event_type in Auth Blocking JWT token', () => { - const authBlockingTokenWithValidEventType = mocks.generateAuthBlockingToken({ - payload: { - event_type: 'beforeSendEmail', - }, - }); - return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithValidEventType, false, undefined) - .should.eventually.be.fulfilled; - }); - - it('should throw an error for invalid event_type in Auth Blocking JWT token', () => { - const authBlockingTokenWithInvalidEventType = mocks.generateAuthBlockingToken({ - payload: { - event_type: 'invalidEventType', - }, - }); - return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithInvalidEventType, false, undefined) - .should.eventually.be.rejected; - }); - - it('should throw an error when event_type is missing in Auth Blocking JWT token', () => { - const authBlockingTokenWithNoEventType = mocks.generateAuthBlockingToken({ - payload: {}, + const eventTypes = ['beforeSendSms', 'beforeSendEmail']; + eventTypes.forEach((eventType) => { + it('should not decode sub when event_type is ${eventType}', async () => { + const mockAuthBlockingToken = mocks.generateAuthBlockingToken(undefined, { + event_type: eventType, + }); + const decoded = await authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined); + console.log("DECODED= ", JSON.stringify(decoded)); + return; }); - return authBlockingTokenVerifier._verifyAuthBlockingToken(authBlockingTokenWithNoEventType, false, undefined) - .should.eventually.be.rejected; }); }); }); From 67b09fde0392468526e620a96f4cd9d0fb0cbf2a Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 18 Mar 2024 14:06:58 -0700 Subject: [PATCH 11/15] unit test --- test/unit/auth/token-verifier.spec.ts | 36 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 998ed914cf..7a71e669de 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -781,15 +781,37 @@ describe('FirebaseTokenVerifier', () => { .should.eventually.be.rejectedWith('Firebase Auth Blocking token has no "kid" claim.'); }); - const eventTypes = ['beforeSendSms', 'beforeSendEmail']; - eventTypes.forEach((eventType) => { - it('should not decode sub when event_type is ${eventType}', async () => { - const mockAuthBlockingToken = mocks.generateAuthBlockingToken(undefined, { + const eventTypesWithoutUid = ['beforeSendSms', 'beforeSendEmail']; + eventTypesWithoutUid.forEach((eventType) => { + it('should not throw error on invalid `sub` when event_type is "' + eventType , async () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ + subject: '' + }, { event_type: eventType, }); - const decoded = await authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined); - console.log("DECODED= ", JSON.stringify(decoded)); - return; + return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) + .should.eventually.be.fulfilled; + }); + }); + + const eventTypesWithUid = ['beforeCreate', 'beforeSignIn', undefined]; + eventTypesWithUid.forEach((eventType) => { + it('should not throw error on invalid `sub` when event_type is "' + eventType , async () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ + subject: '' + }, { + event_type: eventType, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) + .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty string "sub" (subject) claim.'); }); }); }); From 5a41523af9efa361d35a778c4252ab515be3f3b1 Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 18 Mar 2024 14:10:17 -0700 Subject: [PATCH 12/15] add comment per code review --- src/auth/token-verifier.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 79d694f73f..d9231c8b05 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -531,6 +531,8 @@ export class FirebaseTokenVerifier { payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; } else if (!(payload.event_type !== undefined && (payload.event_type === 'beforeSendSms' || payload.event_type === 'beforeSendEmail'))) { + // excluding `beforeSendSms` and `beforeSendEmail` from processing `sub` as there is no user record available. + // `sub` is the same as `uid` which is part of the user record. if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { From df42f44d771625e41a6cf7dc7bec78984ae94201 Mon Sep 17 00:00:00 2001 From: pragatimodi <110490169+pragatimodi@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:04:24 -0700 Subject: [PATCH 13/15] Apply suggestions from code review Co-authored-by: Kevin Cheung --- src/auth/token-verifier.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index d9231c8b05..73fc8f678c 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -536,10 +536,10 @@ export class FirebaseTokenVerifier { if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === '') { - errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + + errorMessage = `${this.tokenInfo.jwtName} has an empty "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub.length > 128) { - errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + + errorMessage = `${this.tokenInfo.jwtName} has a "sub" (subject) claim longer than 128 characters.` + verifyJwtTokenDocsMessage; } } From 44b75845068f4a006d731fb683b2d754fb63d8a4 Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 21 Mar 2024 11:19:15 -0700 Subject: [PATCH 14/15] fix unit test messages --- test/unit/auth/token-verifier.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 7a71e669de..2684c122eb 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -341,7 +341,7 @@ describe('FirebaseTokenVerifier', () => { }); return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has "sub" (subject) claim longer than 128 characters'); + .should.eventually.be.rejectedWith('Firebase ID token has a "sub" (subject) claim longer than 128 characters'); }); }); @@ -659,7 +659,7 @@ describe('FirebaseTokenVerifier', () => { return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) .should.eventually.be.rejectedWith( - 'Firebase Auth Blocking token has "sub" (subject) claim longer than 128 characters'); + 'Firebase Auth Blocking token has a "sub" (subject) claim longer than 128 characters'); }); }); @@ -783,7 +783,7 @@ describe('FirebaseTokenVerifier', () => { const eventTypesWithoutUid = ['beforeSendSms', 'beforeSendEmail']; eventTypesWithoutUid.forEach((eventType) => { - it('should not throw error on invalid `sub` when event_type is "' + eventType , async () => { + it('should not throw error on invalid `sub` when event_type is "' + eventType + '"' , async () => { const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') .resolves(); stubs.push(verifierStub); @@ -800,7 +800,7 @@ describe('FirebaseTokenVerifier', () => { const eventTypesWithUid = ['beforeCreate', 'beforeSignIn', undefined]; eventTypesWithUid.forEach((eventType) => { - it('should not throw error on invalid `sub` when event_type is "' + eventType , async () => { + it('should not throw error on invalid `sub` when event_type is "' + eventType + '"', async () => { const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') .resolves(); stubs.push(verifierStub); @@ -811,7 +811,7 @@ describe('FirebaseTokenVerifier', () => { event_type: eventType, }); return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) - .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty string "sub" (subject) claim.'); + .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty "sub" (subject) claim. See https://cloud.google.com/identity-platform/docs/blocking-functions for details on how to retrieve an Auth Blocking token.'); }); }); }); From dc3c8251da88e3164c383ff837dfb6b945f1a636 Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 21 Mar 2024 13:11:04 -0700 Subject: [PATCH 15/15] fix lint --- test/unit/auth/token-verifier.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 2684c122eb..2d01678ef7 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -341,7 +341,8 @@ describe('FirebaseTokenVerifier', () => { }); return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has a "sub" (subject) claim longer than 128 characters'); + .should.eventually.be.rejectedWith('Firebase ID token has a "sub" (subject) claim longer than 128 ' + + 'characters'); }); }); @@ -811,7 +812,9 @@ describe('FirebaseTokenVerifier', () => { event_type: eventType, }); return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) - .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty "sub" (subject) claim. See https://cloud.google.com/identity-platform/docs/blocking-functions for details on how to retrieve an Auth Blocking token.'); + .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty "sub" (subject) claim.' + + ' See https://cloud.google.com/identity-platform/docs/blocking-functions for details on how to retrieve an' + + ' Auth Blocking token.'); }); }); });