Skip to content

Commit 8044f7d

Browse files
committed
chore: Move Token verification logic to util
1 parent 93362d5 commit 8044f7d

File tree

5 files changed

+102
-39
lines changed

5 files changed

+102
-39
lines changed

src/auth/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import * as validator from '../utils/validator';
3131
import { auth } from './index';
3232
import {
3333
FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier
34-
} from './token-verifier';
34+
} from '../utils/token-verifier';
3535
import {
3636
SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse,
3737
} from './auth-config';

src/utils/error.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,28 @@ export class AppErrorCodes {
338338
public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response';
339339
}
340340

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+
341359
/**
342360
* Auth client error codes and their default messages.
343361
*/
344-
export class AuthClientErrorCode {
362+
export class AuthClientErrorCode extends BaseClientErrorCode {
345363
public static BILLING_NOT_ENABLED = {
346364
code: 'billing-not-enabled',
347365
message: 'Feature requires billing to be enabled.',

src/auth/token-verifier.ts renamed to src/utils/token-verifier.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error';
18-
import * as util from '../utils/index';
19-
import * as validator from '../utils/validator';
17+
import * as util from './index';
18+
import * as validator from './validator';
2019
import * as jwt from 'jsonwebtoken';
21-
import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request';
20+
import { HttpClient, HttpRequestConfig, HttpError } from './api-request';
2221
import { FirebaseApp } from '../firebase-app';
23-
import { auth } from './index';
22+
import { ErrorInfo, PrefixedFirebaseError,
23+
BaseClientErrorCode, AuthClientErrorCode, FirebaseAuthError } from './error';
24+
import { auth } from '../auth/index';
2425

2526
import DecodedIdToken = auth.DecodedIdToken;
2627

@@ -43,6 +44,8 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = {
4344
jwtName: 'Firebase ID token',
4445
shortName: 'ID token',
4546
expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED,
47+
errorCodeType: AuthClientErrorCode,
48+
errorType: FirebaseAuthError,
4649
};
4750

4851
/** User facing token information related to the Firebase session cookie. */
@@ -52,6 +55,8 @@ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = {
5255
jwtName: 'Firebase session cookie',
5356
shortName: 'session cookie',
5457
expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED,
58+
errorCodeType: AuthClientErrorCode,
59+
errorType: FirebaseAuthError,
5560
};
5661

5762
/** Interface that defines token related user facing information. */
@@ -66,6 +71,10 @@ export interface FirebaseTokenInfo {
6671
shortName: string;
6772
/** JWT Expiration error code. */
6873
expiredErrorCode: ErrorInfo;
74+
/** Generic error code type. */
75+
errorCodeType: typeof BaseClientErrorCode;
76+
/** Error type. */
77+
errorType: new (info: ErrorInfo, message?: string) => PrefixedFirebaseError;
6978
}
7079

7180
/**
@@ -81,48 +90,48 @@ export class FirebaseTokenVerifier {
8190
private readonly app: FirebaseApp) {
8291

8392
if (!validator.isURL(clientCertUrl)) {
84-
throw new FirebaseAuthError(
85-
AuthClientErrorCode.INVALID_ARGUMENT,
93+
throw new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
8694
'The provided public client certificate URL is an invalid URL.',
8795
);
96+
8897
} else if (!validator.isNonEmptyString(algorithm)) {
89-
throw new FirebaseAuthError(
90-
AuthClientErrorCode.INVALID_ARGUMENT,
98+
throw new this.tokenInfo.errorType(
99+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
91100
'The provided JWT algorithm is an empty string.',
92101
);
93102
} else if (!validator.isURL(issuer)) {
94-
throw new FirebaseAuthError(
95-
AuthClientErrorCode.INVALID_ARGUMENT,
103+
throw new this.tokenInfo.errorType(
104+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
96105
'The provided JWT issuer is an invalid URL.',
97106
);
98107
} else if (!validator.isNonNullObject(tokenInfo)) {
99-
throw new FirebaseAuthError(
100-
AuthClientErrorCode.INVALID_ARGUMENT,
108+
throw new this.tokenInfo.errorType(
109+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
101110
'The provided JWT information is not an object or null.',
102111
);
103112
} else if (!validator.isURL(tokenInfo.url)) {
104-
throw new FirebaseAuthError(
105-
AuthClientErrorCode.INVALID_ARGUMENT,
113+
throw new this.tokenInfo.errorType(
114+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
106115
'The provided JWT verification documentation URL is invalid.',
107116
);
108117
} else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) {
109-
throw new FirebaseAuthError(
110-
AuthClientErrorCode.INVALID_ARGUMENT,
118+
throw new this.tokenInfo.errorType(
119+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
111120
'The JWT verify API name must be a non-empty string.',
112121
);
113122
} else if (!validator.isNonEmptyString(tokenInfo.jwtName)) {
114-
throw new FirebaseAuthError(
115-
AuthClientErrorCode.INVALID_ARGUMENT,
123+
throw new this.tokenInfo.errorType(
124+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
116125
'The JWT public full name must be a non-empty string.',
117126
);
118127
} else if (!validator.isNonEmptyString(tokenInfo.shortName)) {
119-
throw new FirebaseAuthError(
120-
AuthClientErrorCode.INVALID_ARGUMENT,
128+
throw new this.tokenInfo.errorType(
129+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
121130
'The JWT public short name must be a non-empty string.',
122131
);
123132
} else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || !('code' in tokenInfo.expiredErrorCode)) {
124-
throw new FirebaseAuthError(
125-
AuthClientErrorCode.INVALID_ARGUMENT,
133+
throw new this.tokenInfo.errorType(
134+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
126135
'The JWT expiration error code must be a non-null ErrorInfo object.',
127136
);
128137
}
@@ -141,8 +150,8 @@ export class FirebaseTokenVerifier {
141150
*/
142151
public verifyJWT(jwtToken: string, isEmulator = false): Promise<DecodedIdToken> {
143152
if (!validator.isString(jwtToken)) {
144-
throw new FirebaseAuthError(
145-
AuthClientErrorCode.INVALID_ARGUMENT,
153+
throw new this.tokenInfo.errorType(
154+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
146155
`First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`,
147156
);
148157
}
@@ -159,8 +168,8 @@ export class FirebaseTokenVerifier {
159168
isEmulator: boolean
160169
): Promise<DecodedIdToken> {
161170
if (!validator.isNonEmptyString(projectId)) {
162-
throw new FirebaseAuthError(
163-
AuthClientErrorCode.INVALID_CREDENTIAL,
171+
throw new this.tokenInfo.errorType(
172+
this.tokenInfo.errorCodeType.INVALID_CREDENTIAL,
164173
'Must initialize app with a cert credential or set your Firebase project ID as the ' +
165174
`GOOGLE_CLOUD_PROJECT environment variable to call ${this.tokenInfo.verifyApiName}.`,
166175
);
@@ -217,7 +226,7 @@ export class FirebaseTokenVerifier {
217226
verifyJwtTokenDocsMessage;
218227
}
219228
if (errorMessage) {
220-
return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage));
229+
return Promise.reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, errorMessage));
221230
}
222231

223232
if (isEmulator) {
@@ -228,8 +237,8 @@ export class FirebaseTokenVerifier {
228237
return this.fetchPublicKeys().then((publicKeys) => {
229238
if (!Object.prototype.hasOwnProperty.call(publicKeys, header.kid)) {
230239
return Promise.reject(
231-
new FirebaseAuthError(
232-
AuthClientErrorCode.INVALID_ARGUMENT,
240+
new this.tokenInfo.errorType(
241+
this.tokenInfo.errorCodeType.INVALID_ARGUMENT,
233242
`${this.tokenInfo.jwtName} has "kid" claim which does not correspond to a known public key. ` +
234243
`Most likely the ${this.tokenInfo.shortName} is expired, so get a fresh token from your ` +
235244
'client app and try again.',
@@ -264,12 +273,12 @@ export class FirebaseTokenVerifier {
264273
const errorMessage = `${this.tokenInfo.jwtName} has expired. Get a fresh ${this.tokenInfo.shortName}` +
265274
` from your client app and try again (auth/${this.tokenInfo.expiredErrorCode.code}).` +
266275
verifyJwtTokenDocsMessage;
267-
return reject(new FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage));
276+
return reject(new this.tokenInfo.errorType(this.tokenInfo.expiredErrorCode, errorMessage));
268277
} else if (error.name === 'JsonWebTokenError') {
269278
const errorMessage = `${this.tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage;
270-
return reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage));
279+
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, errorMessage));
271280
}
272-
return reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, error.message));
281+
return reject(new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INVALID_ARGUMENT, error.message));
273282
} else {
274283
const decodedIdToken = (decodedToken as DecodedIdToken);
275284
decodedIdToken.uid = decodedIdToken.sub;
@@ -329,7 +338,7 @@ export class FirebaseTokenVerifier {
329338
} else {
330339
errorMessage += `${resp.text}`;
331340
}
332-
throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, errorMessage);
341+
throw new this.tokenInfo.errorType(this.tokenInfo.errorCodeType.INTERNAL_ERROR, errorMessage);
333342
}
334343
throw err;
335344
});

test/unit/auth/auth.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error';
3737

3838
import * as validator from '../../../src/utils/validator';
39-
import { FirebaseTokenVerifier } from '../../../src/auth/token-verifier';
39+
import { FirebaseTokenVerifier } from '../../../src/utils/token-verifier';
4040
import {
4141
OIDCConfig, SAMLConfig, OIDCConfigServerResponse, SAMLConfigServerResponse,
4242
} from '../../../src/auth/auth-config';

test/unit/auth/token-verifier.spec.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ import LegacyFirebaseTokenGenerator = require('firebase-token-generator');
2929

3030
import * as mocks from '../../resources/mocks';
3131
import { FirebaseTokenGenerator, ServiceAccountSigner } from '../../../src/auth/token-generator';
32-
import * as verifier from '../../../src/auth/token-verifier';
32+
import * as verifier from '../../../src/utils/token-verifier';
3333

3434
import { ServiceAccountCredential } from '../../../src/credential/credential-internal';
35-
import { AuthClientErrorCode } from '../../../src/utils/error';
35+
import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error';
3636
import { FirebaseApp } from '../../../src/firebase-app';
3737
import { Algorithm } from 'jsonwebtoken';
3838

@@ -160,6 +160,8 @@ describe('FirebaseTokenVerifier', () => {
160160
jwtName: 'Important Token',
161161
shortName: 'token',
162162
expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT,
163+
errorCodeType: AuthClientErrorCode,
164+
errorType: FirebaseAuthError,
163165
},
164166
app,
165167
);
@@ -224,6 +226,8 @@ describe('FirebaseTokenVerifier', () => {
224226
jwtName: 'Important Token',
225227
shortName: 'token',
226228
expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT,
229+
errorCodeType: AuthClientErrorCode,
230+
errorType: FirebaseAuthError,
227231
},
228232
app,
229233
);
@@ -245,6 +249,8 @@ describe('FirebaseTokenVerifier', () => {
245249
jwtName: invalidJwtName as any,
246250
shortName: 'token',
247251
expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT,
252+
errorCodeType: AuthClientErrorCode,
253+
errorType: FirebaseAuthError,
248254
},
249255
app,
250256
);
@@ -266,6 +272,8 @@ describe('FirebaseTokenVerifier', () => {
266272
jwtName: 'Important Token',
267273
shortName: invalidShortName as any,
268274
expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT,
275+
errorCodeType: AuthClientErrorCode,
276+
errorType: FirebaseAuthError,
269277
},
270278
app,
271279
);
@@ -287,6 +295,8 @@ describe('FirebaseTokenVerifier', () => {
287295
jwtName: 'Important Token',
288296
shortName: 'token',
289297
expiredErrorCode: invalidExpiredErrorCode as any,
298+
errorCodeType: AuthClientErrorCode,
299+
errorType: FirebaseAuthError,
290300
},
291301
app,
292302
);
@@ -295,6 +305,32 @@ describe('FirebaseTokenVerifier', () => {
295305
});
296306
});
297307

308+
const errorTypes = [
309+
{ type: FirebaseAuthError, code: AuthClientErrorCode },
310+
];
311+
errorTypes.forEach((errorType) => {
312+
it('should throw with the correct error type set in token info', () => {
313+
expect(() => {
314+
new verifier.FirebaseTokenVerifier(
315+
'https://www.example.com/publicKeys',
316+
'RS256',
317+
'https://www.example.com/issuer/',
318+
{
319+
url: 'https://docs.example.com/verify-tokens',
320+
verifyApiName: 'verifyToken()',
321+
jwtName: 'Important Token',
322+
shortName: '',
323+
expiredErrorCode: errorType.code.INVALID_ARGUMENT,
324+
errorCodeType: errorType.code,
325+
errorType: errorType.type,
326+
},
327+
app,
328+
);
329+
}).to.throw(errorType.type)
330+
.with.property('code').and.match(/(auth|messaging)\/argument-error/);
331+
});
332+
});
333+
298334
describe('verifyJWT()', () => {
299335
let mockedRequests: nock.Scope[] = [];
300336

0 commit comments

Comments
 (0)