1+ /*!
2+ * @license
3+ * Copyright 2025 Google LLC
4+ *
5+ * Licensed under the Apache License, Version 2.0 (the "License");
6+ * you may not use this file except in compliance with the License.
7+ * You may obtain a copy of the License at
8+ *
9+ * http://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ */
17+
118import { App } from '../app' ;
2- import { FpnvErrorCode , ErrorInfo , FirebasePnvError } from '../utils/error' ;
19+ import { FpnvErrorCode , FirebasePnvError , ErrorInfo } from '../utils/error' ;
20+ import { FirebasePhoneNumberTokenInfo , FpnvToken } from './fpnv-api' ;
321import * as util from '../utils/index' ;
422import * as validator from '../utils/validator' ;
523import {
624 DecodedToken , decodeJwt , JwtError , JwtErrorCode ,
725 PublicKeySignatureVerifier , ALGORITHM_ES256 , SignatureVerifier ,
826} from '../utils/jwt' ;
927
10- export interface FpnvToken {
11- aud : string ;
12- auth_time : number ;
13- exp : number ;
14- iat : number ;
15- iss : string ;
16- sub : string ;
17-
18- getPhoneNumber ( ) : string ;
19-
20- /**
21- * Other arbitrary claims included in the ID token.
22- */
23- [ key : string ] : any ;
24- }
25-
2628const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' ;
2729
28- export const PN_TOKEN_INFO : FirebasePhoneNumberTokenInfo = {
30+
31+ const PN_TOKEN_INFO : FirebasePhoneNumberTokenInfo = {
2932 url : 'https://firebase.google.com/docs/phone-number-verification' ,
3033 verifyApiName : 'verifyToken()' ,
3134 jwtName : 'Firebase Phone Verification token' ,
3235 shortName : 'FPNV token' ,
3336 typ : 'JWT' ,
34- expiredErrorCode : FpnvErrorCode . COMMON_ISSUE ,
37+ expiredErrorCode : FpnvErrorCode . EXPIRED_TOKEN ,
3538} ;
3639
37- export interface FirebasePhoneNumberTokenInfo {
40+ interface FirebasePhoneNumberTokenInfo {
3841 /** Documentation URL. */
3942 url : string ;
4043 /** verify API name. */
@@ -63,42 +66,42 @@ export class FirebasePhoneNumberTokenVerifier {
6366
6467 if ( ! validator . isURL ( clientCertUrl ) ) {
6568 throw new FirebasePnvError (
66- FpnvErrorCode . COMMON_ISSUE ,
69+ FpnvErrorCode . INVALID_ARGUMENT ,
6770 'The provided public client certificate URL is an invalid URL.' ,
6871 ) ;
6972 } else if ( ! validator . isURL ( issuer ) ) {
7073 throw new FirebasePnvError (
71- FpnvErrorCode . COMMON_ISSUE ,
74+ FpnvErrorCode . INVALID_ARGUMENT ,
7275 'The provided JWT issuer is an invalid URL.' ,
7376 ) ;
7477 } else if ( ! validator . isNonNullObject ( tokenInfo ) ) {
7578 throw new FirebasePnvError (
76- FpnvErrorCode . COMMON_ISSUE ,
79+ FpnvErrorCode . INVALID_ARGUMENT ,
7780 'The provided JWT information is not an object or null.' ,
7881 ) ;
7982 } else if ( ! validator . isURL ( tokenInfo . url ) ) {
8083 throw new FirebasePnvError (
81- FpnvErrorCode . COMMON_ISSUE ,
84+ FpnvErrorCode . INVALID_ARGUMENT ,
8285 'The provided JWT verification documentation URL is invalid.' ,
8386 ) ;
8487 } else if ( ! validator . isNonEmptyString ( tokenInfo . verifyApiName ) ) {
8588 throw new FirebasePnvError (
86- FpnvErrorCode . COMMON_ISSUE ,
89+ FpnvErrorCode . INVALID_ARGUMENT ,
8790 'The JWT verify API name must be a non-empty string.' ,
8891 ) ;
8992 } else if ( ! validator . isNonEmptyString ( tokenInfo . jwtName ) ) {
9093 throw new FirebasePnvError (
91- FpnvErrorCode . COMMON_ISSUE ,
94+ FpnvErrorCode . INVALID_ARGUMENT ,
9295 'The JWT public full name must be a non-empty string.' ,
9396 ) ;
9497 } else if ( ! validator . isNonEmptyString ( tokenInfo . shortName ) ) {
9598 throw new FirebasePnvError (
96- FpnvErrorCode . COMMON_ISSUE ,
99+ FpnvErrorCode . INVALID_ARGUMENT ,
97100 'The JWT public short name must be a non-empty string.' ,
98101 ) ;
99102 } else if ( ! validator . isNonNullObject ( tokenInfo . expiredErrorCode ) || ! ( 'code' in tokenInfo . expiredErrorCode ) ) {
100103 throw new FirebasePnvError (
101- FpnvErrorCode . COMMON_ISSUE ,
104+ FpnvErrorCode . INVALID_ARGUMENT ,
102105 'The JWT expiration error code must be a non-null ErrorInfo object.' ,
103106 ) ;
104107 }
@@ -113,7 +116,7 @@ export class FirebasePhoneNumberTokenVerifier {
113116 public async verifyJWT ( jwtToken : string ) : Promise < FpnvToken > {
114117 if ( ! validator . isString ( jwtToken ) ) {
115118 throw new FirebasePnvError (
116- FpnvErrorCode . COMMON_ISSUE ,
119+ FpnvErrorCode . PROJECT_NOT_FOUND ,
117120 `First argument to ${ this . tokenInfo . verifyApiName } must be a ${ this . tokenInfo . jwtName } string.` ,
118121 ) ;
119122 }
@@ -129,7 +132,7 @@ export class FirebasePhoneNumberTokenVerifier {
129132 const projectId = await util . findProjectId ( this . app ) ;
130133 if ( ! validator . isNonEmptyString ( projectId ) ) {
131134 throw new FirebasePnvError (
132- FpnvErrorCode . COMMON_ISSUE ,
135+ FpnvErrorCode . PROJECT_NOT_FOUND ,
133136 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
134137 `GOOGLE_CLOUD_PROJECT environment variable to call ${ this . tokenInfo . verifyApiName } .` ) ;
135138 }
@@ -156,10 +159,10 @@ export class FirebasePhoneNumberTokenVerifier {
156159 const errorMessage = `Decoding ${ this . tokenInfo . jwtName } failed. Make sure you passed ` +
157160 `the entire string JWT which represents ${ this . shortNameArticle } ` +
158161 `${ this . tokenInfo . shortName } .` + verifyJwtTokenDocsMessage ;
159- throw new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE ,
162+ throw new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT ,
160163 errorMessage ) ;
161164 }
162- throw new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE , err . message ) ;
165+ throw new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT , err . message ) ;
163166 }
164167 }
165168
@@ -183,14 +186,14 @@ export class FirebasePhoneNumberTokenVerifier {
183186 errorMessage = `${ this . tokenInfo . jwtName } has no "kid" claim.` ;
184187 errorMessage += verifyJwtTokenDocsMessage ;
185188 } else if ( header . alg !== ALGORITHM_ES256 ) {
186- errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected " ` + ALGORITHM_ES256 + '" but got ' +
187- '"' + header . alg + '".' + verifyJwtTokenDocsMessage ;
189+ errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected ` +
190+ `" ${ ALGORITHM_ES256 } " but got " ${ header . alg } ". ${ verifyJwtTokenDocsMessage } ` ;
188191 } else if ( header . typ !== this . tokenInfo . typ ) {
189192 errorMessage = `${ this . tokenInfo . jwtName } has incorrect typ. Expected "${ this . tokenInfo . typ } " but got ` +
190193 '"' + header . typ + '".' + verifyJwtTokenDocsMessage ;
191194 }
192195 // FPNV Token
193- else if ( ! ( ( payload . aud as string [ ] ) . some ( item => item === this . issuer + projectId ) ) ) {
196+ else if ( ! ( ( Array . isArray ( payload . aud ) ? payload . aud : [ ] ) . some ( item => item === this . issuer + projectId ) ) ) {
194197 errorMessage = `${ this . tokenInfo . jwtName } has incorrect "aud" (audience) claim. Expected "` +
195198 this . issuer + projectId + '" to be one of "' + payload . aud + '".' + projectIdMatchMessage +
196199 verifyJwtTokenDocsMessage ;
@@ -202,7 +205,7 @@ export class FirebasePhoneNumberTokenVerifier {
202205 }
203206
204207 if ( errorMessage ) {
205- throw new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE , errorMessage ) ;
208+ throw new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT , errorMessage ) ;
206209 }
207210 }
208211
@@ -224,14 +227,14 @@ export class FirebasePhoneNumberTokenVerifier {
224227 return new FirebasePnvError ( this . tokenInfo . expiredErrorCode , errorMessage ) ;
225228 } else if ( error . code === JwtErrorCode . INVALID_SIGNATURE ) {
226229 const errorMessage = `${ this . tokenInfo . jwtName } has invalid signature.` + verifyJwtTokenDocsMessage ;
227- return new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE , errorMessage ) ;
230+ return new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT , errorMessage ) ;
228231 } else if ( error . code === JwtErrorCode . NO_MATCHING_KID ) {
229232 const errorMessage = `${ this . tokenInfo . jwtName } has "kid" claim which does not ` +
230233 `correspond to a known public key. Most likely the ${ this . tokenInfo . shortName } ` +
231234 'is expired, so get a fresh token from your client app and try again.' ;
232- return new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE , errorMessage ) ;
235+ return new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT , errorMessage ) ;
233236 }
234- return new FirebasePnvError ( FpnvErrorCode . COMMON_ISSUE , error . message ) ;
237+ return new FirebasePnvError ( FpnvErrorCode . INVALID_ARGUMENT , error . message ) ;
235238 }
236239
237240}
0 commit comments