Skip to content

Commit 7a1d9ea

Browse files
committed
Moved Auth related items to token-verifier-util
1 parent 8044f7d commit 7a1d9ea

File tree

5 files changed

+150
-128
lines changed

5 files changed

+150
-128
lines changed

src/auth/auth.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/erro
2929
import * as utils from '../utils/index';
3030
import * as validator from '../utils/validator';
3131
import { auth } from './index';
32-
import {
33-
FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier
34-
} from '../utils/token-verifier';
32+
import { FirebaseTokenVerifier } from '../utils/token-verifier';
33+
import { createSessionCookieVerifier, createIdTokenVerifier } from './token-verifier-util';
3534
import {
3635
SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse,
3736
} from './auth-config';

src/auth/token-verifier-util.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*!
2+
* Copyright 2021 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { FirebaseApp } from '../firebase-app';
18+
import { AuthClientErrorCode, ErrorCodeConfig, FirebaseAuthError } from '../utils/error';
19+
import { FirebaseTokenInfo, FirebaseTokenVerifier } from '../utils/token-verifier';
20+
21+
const ALGORITHM_RS256 = 'RS256';
22+
23+
// URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
24+
// Auth ID tokens)
25+
const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/[email protected]';
26+
27+
// URL containing the public keys for Firebase session cookies. This will be updated to a different URL soon.
28+
const SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys';
29+
30+
/** Error codes that matches the FirebaseAuthError type */
31+
const AUTH_ERROR_CODE_CONFIG: ErrorCodeConfig = {
32+
invalidArg: AuthClientErrorCode.INVALID_ARGUMENT,
33+
invalidCredential: AuthClientErrorCode.INVALID_CREDENTIAL,
34+
internalError: AuthClientErrorCode.INTERNAL_ERROR,
35+
}
36+
37+
/** User facing token information related to the Firebase ID token. */
38+
export const ID_TOKEN_INFO: FirebaseTokenInfo = {
39+
url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens',
40+
verifyApiName: 'verifyIdToken()',
41+
jwtName: 'Firebase ID token',
42+
shortName: 'ID token',
43+
expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED,
44+
errorCodeConfig: AUTH_ERROR_CODE_CONFIG,
45+
errorType: FirebaseAuthError,
46+
};
47+
48+
/** User facing token information related to the Firebase session cookie. */
49+
export const SESSION_COOKIE_INFO: FirebaseTokenInfo = {
50+
url: 'https://firebase.google.com/docs/auth/admin/manage-cookies',
51+
verifyApiName: 'verifySessionCookie()',
52+
jwtName: 'Firebase session cookie',
53+
shortName: 'session cookie',
54+
expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED,
55+
errorCodeConfig: AUTH_ERROR_CODE_CONFIG,
56+
errorType: FirebaseAuthError,
57+
};
58+
59+
/**
60+
* Creates a new FirebaseTokenVerifier to verify Firebase ID tokens.
61+
*
62+
* @param {FirebaseApp} app Firebase app instance.
63+
* @return {FirebaseTokenVerifier}
64+
*/
65+
export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier {
66+
return new FirebaseTokenVerifier(
67+
CLIENT_CERT_URL,
68+
ALGORITHM_RS256,
69+
'https://securetoken.google.com/',
70+
ID_TOKEN_INFO,
71+
app
72+
);
73+
}
74+
75+
/**
76+
* Creates a new FirebaseTokenVerifier to verify Firebase session cookies.
77+
*
78+
* @param {FirebaseApp} app Firebase app instance.
79+
* @return {FirebaseTokenVerifier}
80+
*/
81+
export function createSessionCookieVerifier(app: FirebaseApp): FirebaseTokenVerifier {
82+
return new FirebaseTokenVerifier(
83+
SESSION_COOKIE_CERT_URL,
84+
ALGORITHM_RS256,
85+
'https://session.firebase.google.com/',
86+
SESSION_COOKIE_INFO,
87+
app
88+
);
89+
}

src/utils/error.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ interface ServerToClientCode {
3333
[code: string]: string;
3434
}
3535

36+
/**
37+
* Defines a type that stores commonly used error codes.
38+
*/
39+
export interface ErrorCodeConfig {
40+
invalidArg: ErrorInfo;
41+
invalidCredential: ErrorInfo;
42+
internalError: ErrorInfo;
43+
}
44+
3645
/**
3746
* Firebase error code structure. This extends Error.
3847
*
@@ -338,28 +347,10 @@ export class AppErrorCodes {
338347
public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response';
339348
}
340349

341-
/**
342-
* Base class for client error codes and their default messages.
343-
*/
344-
export class BaseClientErrorCode {
345-
public static INVALID_ARGUMENT = {
346-
code: 'argument-error',
347-
message: 'Invalid argument provided.',
348-
};
349-
public static INVALID_CREDENTIAL = {
350-
code: 'invalid-credential',
351-
message: 'Invalid credential object provided.',
352-
};
353-
public static INTERNAL_ERROR = {
354-
code: 'internal-error',
355-
message: 'An internal error has occurred.',
356-
};
357-
}
358-
359350
/**
360351
* Auth client error codes and their default messages.
361352
*/
362-
export class AuthClientErrorCode extends BaseClientErrorCode {
353+
export class AuthClientErrorCode {
363354
public static BILLING_NOT_ENABLED = {
364355
code: 'billing-not-enabled',
365356
message: 'Feature requires billing to be enabled.',

src/utils/token-verifier.ts

Lines changed: 21 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -19,46 +19,14 @@ import * as validator from './validator';
1919
import * as jwt from 'jsonwebtoken';
2020
import { HttpClient, HttpRequestConfig, HttpError } from './api-request';
2121
import { FirebaseApp } from '../firebase-app';
22-
import { ErrorInfo, PrefixedFirebaseError,
23-
BaseClientErrorCode, AuthClientErrorCode, FirebaseAuthError } from './error';
22+
import { ErrorCodeConfig, ErrorInfo, PrefixedFirebaseError } from './error';
2423
import { auth } from '../auth/index';
2524

2625
import DecodedIdToken = auth.DecodedIdToken;
2726

2827
// Audience to use for Firebase Auth Custom tokens
2928
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
3029

31-
export const ALGORITHM_RS256 = 'RS256';
32-
33-
// URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
34-
// Auth ID tokens)
35-
const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/[email protected]';
36-
37-
// URL containing the public keys for Firebase session cookies. This will be updated to a different URL soon.
38-
const SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys';
39-
40-
/** User facing token information related to the Firebase ID token. */
41-
export const ID_TOKEN_INFO: FirebaseTokenInfo = {
42-
url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens',
43-
verifyApiName: 'verifyIdToken()',
44-
jwtName: 'Firebase ID token',
45-
shortName: 'ID token',
46-
expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED,
47-
errorCodeType: AuthClientErrorCode,
48-
errorType: FirebaseAuthError,
49-
};
50-
51-
/** User facing token information related to the Firebase session cookie. */
52-
export const SESSION_COOKIE_INFO: FirebaseTokenInfo = {
53-
url: 'https://firebase.google.com/docs/auth/admin/manage-cookies',
54-
verifyApiName: 'verifySessionCookie()',
55-
jwtName: 'Firebase session cookie',
56-
shortName: 'session cookie',
57-
expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED,
58-
errorCodeType: AuthClientErrorCode,
59-
errorType: FirebaseAuthError,
60-
};
61-
6230
/** Interface that defines token related user facing information. */
6331
export interface FirebaseTokenInfo {
6432
/** Documentation URL. */
@@ -71,9 +39,9 @@ export interface FirebaseTokenInfo {
7139
shortName: string;
7240
/** JWT Expiration error code. */
7341
expiredErrorCode: ErrorInfo;
74-
/** Generic error code type. */
75-
errorCodeType: typeof BaseClientErrorCode;
76-
/** Error type. */
42+
/** Error code config of the public error type. */
43+
errorCodeConfig: ErrorCodeConfig;
44+
/** Public error type. */
7745
errorType: new (info: ErrorInfo, message?: string) => PrefixedFirebaseError;
7846
}
7947

@@ -90,48 +58,49 @@ export class FirebaseTokenVerifier {
9058
private readonly app: FirebaseApp) {
9159

9260
if (!validator.isURL(clientCertUrl)) {
93-
throw new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
61+
throw new this.tokenInfo.errorType(
62+
this.tokenInfo.errorCodeConfig.invalidArg,
9463
'The provided public client certificate URL is an invalid URL.',
9564
);
9665

9766
} else if (!validator.isNonEmptyString(algorithm)) {
9867
throw new this.tokenInfo.errorType(
99-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
68+
this.tokenInfo.errorCodeConfig.invalidArg,
10069
'The provided JWT algorithm is an empty string.',
10170
);
10271
} else if (!validator.isURL(issuer)) {
10372
throw new this.tokenInfo.errorType(
104-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
73+
this.tokenInfo.errorCodeConfig.invalidArg,
10574
'The provided JWT issuer is an invalid URL.',
10675
);
10776
} else if (!validator.isNonNullObject(tokenInfo)) {
10877
throw new this.tokenInfo.errorType(
109-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
78+
this.tokenInfo.errorCodeConfig.invalidArg,
11079
'The provided JWT information is not an object or null.',
11180
);
11281
} else if (!validator.isURL(tokenInfo.url)) {
11382
throw new this.tokenInfo.errorType(
114-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
83+
this.tokenInfo.errorCodeConfig.invalidArg,
11584
'The provided JWT verification documentation URL is invalid.',
11685
);
11786
} else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) {
11887
throw new this.tokenInfo.errorType(
119-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
88+
this.tokenInfo.errorCodeConfig.invalidArg,
12089
'The JWT verify API name must be a non-empty string.',
12190
);
12291
} else if (!validator.isNonEmptyString(tokenInfo.jwtName)) {
12392
throw new this.tokenInfo.errorType(
124-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
93+
this.tokenInfo.errorCodeConfig.invalidArg,
12594
'The JWT public full name must be a non-empty string.',
12695
);
12796
} else if (!validator.isNonEmptyString(tokenInfo.shortName)) {
12897
throw new this.tokenInfo.errorType(
129-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
98+
this.tokenInfo.errorCodeConfig.invalidArg,
13099
'The JWT public short name must be a non-empty string.',
131100
);
132101
} else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || !('code' in tokenInfo.expiredErrorCode)) {
133102
throw new this.tokenInfo.errorType(
134-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
103+
this.tokenInfo.errorCodeConfig.invalidArg,
135104
'The JWT expiration error code must be a non-null ErrorInfo object.',
136105
);
137106
}
@@ -151,7 +120,7 @@ export class FirebaseTokenVerifier {
151120
public verifyJWT(jwtToken: string, isEmulator = false): Promise<DecodedIdToken> {
152121
if (!validator.isString(jwtToken)) {
153122
throw new this.tokenInfo.errorType(
154-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
123+
this.tokenInfo.errorCodeConfig.invalidArg,
155124
`First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`,
156125
);
157126
}
@@ -169,7 +138,7 @@ export class FirebaseTokenVerifier {
169138
): Promise<DecodedIdToken> {
170139
if (!validator.isNonEmptyString(projectId)) {
171140
throw new this.tokenInfo.errorType(
172-
this.tokenInfo.errorCodeType.INVALID_CREDENTIAL,
141+
this.tokenInfo.errorCodeConfig.invalidCredential,
173142
'Must initialize app with a cert credential or set your Firebase project ID as the ' +
174143
`GOOGLE_CLOUD_PROJECT environment variable to call ${this.tokenInfo.verifyApiName}.`,
175144
);
@@ -226,7 +195,7 @@ export class FirebaseTokenVerifier {
226195
verifyJwtTokenDocsMessage;
227196
}
228197
if (errorMessage) {
229-
return Promise.reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, errorMessage));
198+
return Promise.reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeConfig.invalidArg, errorMessage));
230199
}
231200

232201
if (isEmulator) {
@@ -238,7 +207,7 @@ export class FirebaseTokenVerifier {
238207
if (!Object.prototype.hasOwnProperty.call(publicKeys, header.kid)) {
239208
return Promise.reject(
240209
new this.tokenInfo.errorType(
241-
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
210+
this.tokenInfo.errorCodeConfig.invalidArg,
242211
`${this.tokenInfo.jwtName} has "kid" claim which does not correspond to a known public key. ` +
243212
`Most likely the ${this.tokenInfo.shortName} is expired, so get a fresh token from your ` +
244213
'client app and try again.',
@@ -276,9 +245,9 @@ export class FirebaseTokenVerifier {
276245
return reject(new this.tokenInfo.errorType(this.tokenInfo.expiredErrorCode, errorMessage));
277246
} else if (error.name === 'JsonWebTokenError') {
278247
const errorMessage = `${this.tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage;
279-
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, errorMessage));
248+
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeConfig.invalidArg, errorMessage));
280249
}
281-
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, error.message));
250+
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeConfig.invalidArg, error.message));
282251
} else {
283252
const decodedIdToken = (decodedToken as DecodedIdToken);
284253
decodedIdToken.uid = decodedIdToken.sub;
@@ -338,41 +307,9 @@ export class FirebaseTokenVerifier {
338307
} else {
339308
errorMessage += `${resp.text}`;
340309
}
341-
throw new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INTERNAL_ERROR, errorMessage);
310+
throw new this.tokenInfo.errorType(this.tokenInfo.errorCodeConfig.internalError, errorMessage);
342311
}
343312
throw err;
344313
});
345314
}
346315
}
347-
348-
/**
349-
* Creates a new FirebaseTokenVerifier to verify Firebase ID tokens.
350-
*
351-
* @param {FirebaseApp} app Firebase app instance.
352-
* @return {FirebaseTokenVerifier}
353-
*/
354-
export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier {
355-
return new FirebaseTokenVerifier(
356-
CLIENT_CERT_URL,
357-
ALGORITHM_RS256,
358-
'https://securetoken.google.com/',
359-
ID_TOKEN_INFO,
360-
app
361-
);
362-
}
363-
364-
/**
365-
* Creates a new FirebaseTokenVerifier to verify Firebase session cookies.
366-
*
367-
* @param {FirebaseApp} app Firebase app instance.
368-
* @return {FirebaseTokenVerifier}
369-
*/
370-
export function createSessionCookieVerifier(app: FirebaseApp): FirebaseTokenVerifier {
371-
return new FirebaseTokenVerifier(
372-
SESSION_COOKIE_CERT_URL,
373-
ALGORITHM_RS256,
374-
'https://session.firebase.google.com/',
375-
SESSION_COOKIE_INFO,
376-
app
377-
);
378-
}

0 commit comments

Comments
 (0)