@@ -19,7 +19,7 @@ import * as jwt from 'jsonwebtoken';
19
19
import { HttpClient , HttpRequestConfig , HttpError } from '../utils/api-request' ;
20
20
import { Agent } from 'http' ;
21
21
22
- const ALGORITHM_RS256 : jwt . Algorithm = 'RS256' as const ;
22
+ export const ALGORITHM_RS256 : jwt . Algorithm = 'RS256' as const ;
23
23
24
24
// `jsonwebtoken` converts errors from the `getKey` callback to its own `JsonWebTokenError` type
25
25
// and prefixes the error message with the following. Use the prefix to identify errors thrown
@@ -29,7 +29,7 @@ const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: ';
29
29
30
30
const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error' ;
31
31
32
- export type Dictionary = { [ key : string ] : any }
32
+ export type Dictionary = { [ key : string ] : any }
33
33
34
34
export type DecodedToken = {
35
35
header : Dictionary ;
@@ -44,14 +44,17 @@ interface KeyFetcher {
44
44
fetchPublicKeys ( ) : Promise < { [ key : string ] : string } > ;
45
45
}
46
46
47
- class UrlKeyFetcher implements KeyFetcher {
47
+ /**
48
+ * Class to fetch public keys from a client certificates URL.
49
+ */
50
+ export class UrlKeyFetcher implements KeyFetcher {
48
51
private publicKeys : { [ key : string ] : string } ;
49
52
private publicKeysExpireAt = 0 ;
50
53
51
54
constructor ( private clientCertUrl : string , private readonly httpAgent ?: Agent ) {
52
55
if ( ! validator . isURL ( clientCertUrl ) ) {
53
56
throw new Error (
54
- 'The provided public client certificate URL is an invalid URL.' ,
57
+ 'The provided public client certificate URL is not a valid URL.' ,
55
58
) ;
56
59
}
57
60
}
@@ -68,6 +71,11 @@ class UrlKeyFetcher implements KeyFetcher {
68
71
return Promise . resolve ( this . publicKeys ) ;
69
72
}
70
73
74
+ /**
75
+ * Checks if the cached public keys need to be refreshed.
76
+ *
77
+ * @returns Whether the keys should be fetched from the client certs url or not.
78
+ */
71
79
private shouldRefresh ( ) : boolean {
72
80
return ! this . publicKeys || this . publicKeysExpireAt <= Date . now ( ) ;
73
81
}
@@ -120,7 +128,7 @@ class UrlKeyFetcher implements KeyFetcher {
120
128
}
121
129
122
130
/**
123
- * Verifies JWT signature with a public key.
131
+ * Class for verifing JWT signature with a public key.
124
132
*/
125
133
export class PublicKeySignatureVerifier implements SignatureVerifier {
126
134
constructor ( private keyFetcher : KeyFetcher ) {
@@ -134,10 +142,31 @@ export class PublicKeySignatureVerifier implements SignatureVerifier {
134
142
}
135
143
136
144
public verify ( token : string ) : Promise < void > {
145
+ if ( ! validator . isString ( token ) ) {
146
+ return Promise . reject ( new JwtError ( JwtErrorCode . INVALID_ARGUMENT ,
147
+ 'The provided token must be a string.' ) ) ;
148
+ }
149
+
137
150
return verifyJwtSignature ( token , getKeyCallback ( this . keyFetcher ) , { algorithms : [ ALGORITHM_RS256 ] } ) ;
138
151
}
139
152
}
140
153
154
+ /**
155
+ * Class for verifing unsigned (emulator) JWTs.
156
+ */
157
+ export class EmulatorSignatureVerifier implements SignatureVerifier {
158
+ public verify ( token : string ) : Promise < void > {
159
+ // Signature checks skipped for emulator; no need to fetch public keys.
160
+ return verifyJwtSignature ( token , '' ) ;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Provides a callback to fetch public keys.
166
+ *
167
+ * @param fetcher KeyFetcher to fetch the keys from.
168
+ * @returns A callback function that can be used to get keys in `jsonwebtoken`.
169
+ */
141
170
function getKeyCallback ( fetcher : KeyFetcher ) : jwt . GetPublicKeyOrSecret {
142
171
return ( header : jwt . JwtHeader , callback : jwt . SigningKeyCallback ) => {
143
172
const kid = header . kid || '' ;
@@ -154,15 +183,22 @@ function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret {
154
183
}
155
184
}
156
185
157
- export class EmulatorSignatureVerifier implements SignatureVerifier {
158
- public verify ( token : string ) : Promise < void > {
159
- // Signature checks skipped for emulator; no need to fetch public keys.
160
- return verifyJwtSignature ( token , '' ) ;
186
+ /**
187
+ * Verifies the signature of a JWT using the provided secret or a function to fetch
188
+ * the secret or public key.
189
+ *
190
+ * @param token The JWT to be verfied.
191
+ * @param secretOrPublicKey The secret or a function to fetch the secret or public key.
192
+ * @param options JWT verification options.
193
+ * @returns A Promise resolving for a token with a valid signature.
194
+ */
195
+ export function verifyJwtSignature ( token : string , secretOrPublicKey : jwt . Secret | jwt . GetPublicKeyOrSecret ,
196
+ options ?: jwt . VerifyOptions ) : Promise < void > {
197
+ if ( ! validator . isString ( token ) ) {
198
+ return Promise . reject ( new JwtError ( JwtErrorCode . INVALID_ARGUMENT ,
199
+ 'The provided token must be a string.' ) ) ;
161
200
}
162
- }
163
201
164
- function verifyJwtSignature ( token : string , secretOrPublicKey : jwt . Secret | jwt . GetPublicKeyOrSecret ,
165
- options ?: jwt . VerifyOptions ) : Promise < void > {
166
202
return new Promise ( ( resolve , reject ) => {
167
203
jwt . verify ( token , secretOrPublicKey , options ,
168
204
( error : jwt . VerifyErrors | null ) => {
@@ -176,12 +212,10 @@ function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret | jwt.G
176
212
} else if ( error . name === 'JsonWebTokenError' ) {
177
213
if ( error . message && error . message . includes ( JWT_CALLBACK_ERROR_PREFIX ) ) {
178
214
const message = error . message . split ( JWT_CALLBACK_ERROR_PREFIX ) . pop ( ) || 'Error fetching public keys.' ;
179
- const code = ( message === NO_MATCHING_KID_ERROR_MESSAGE ) ? JwtErrorCode . KEY_FETCH_ERROR :
180
- JwtErrorCode . INVALID_ARGUMENT ;
215
+ const code = ( message === NO_MATCHING_KID_ERROR_MESSAGE ) ? JwtErrorCode . NO_MATCHING_KID :
216
+ JwtErrorCode . KEY_FETCH_ERROR ;
181
217
return reject ( new JwtError ( code , message ) ) ;
182
218
}
183
- return reject ( new JwtError ( JwtErrorCode . INVALID_SIGNATURE ,
184
- 'The provided token has invalid signature.' ) ) ;
185
219
}
186
220
return reject ( new JwtError ( JwtErrorCode . INVALID_SIGNATURE , error . message ) ) ;
187
221
} ) ;
@@ -190,6 +224,9 @@ function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret | jwt.G
190
224
191
225
/**
192
226
* Decodes general purpose Firebase JWTs.
227
+ *
228
+ * @param jwtToken JWT token to be decoded.
229
+ * @returns Decoded token containing the header and payload.
193
230
*/
194
231
export function decodeJwt ( jwtToken : string ) : Promise < DecodedToken > {
195
232
if ( ! validator . isString ( jwtToken ) ) {
@@ -233,5 +270,6 @@ export enum JwtErrorCode {
233
270
INVALID_CREDENTIAL = 'invalid-credential' ,
234
271
TOKEN_EXPIRED = 'token-expired' ,
235
272
INVALID_SIGNATURE = 'invalid-token' ,
236
- KEY_FETCH_ERROR = 'no-matching-kid-error' ,
273
+ NO_MATCHING_KID = 'no-matching-kid-error' ,
274
+ KEY_FETCH_ERROR = 'key-fetch-error' ,
237
275
}
0 commit comments