-
Notifications
You must be signed in to change notification settings - Fork 390
chore: Move token verification logic to util #1189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
534fcd6
to
8044f7d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked at everything, but looks reasonable. Let's start off with some feedback on the high-level design.
7a1d9ea
to
6719cfa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good. Just a few suggestions for cleaning up the PR.
1ac1020
to
5d3597a
Compare
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the changes. Looks mostly good. Couple of points to revise:
- There's one remaining reference to
auth
from the utils token verifier. - Unit tests could use a bit more attention.
src/utils/token-verifier.ts
Outdated
@@ -213,9 +216,9 @@ export class FirebaseTokenVerifier { | |||
if (error) { | |||
if (error.name === 'TokenExpiredError') { | |||
const errorMessage = `${this.tokenInfo.jwtName} has expired. Get a fresh ${this.tokenInfo.shortName}` + | |||
` from your client app and try again (auth/${this.tokenInfo.expiredErrorCode.code}).` + | |||
` from your client app and try again (auth/${this.tokenInfo.errorCodeConfig.expiredError.code}).` + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a reference to auth
here. Should probably get rid of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh good catch! I actually ended up finding more references to Auth. Ended up using generics in the verify token functions and moved the code to set the user id alias to one level up to auth.ts
.
Let me know what you think. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: the error code prefix. I just removed the auth
prefix, but the error code feels a bit redundant here as the error code will also be included in the public error type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something that will get cleaned up once we fix error handling for this SDK. For now this solution is adequate.
}); | ||
}); | ||
|
||
const invalidErrorCodeConfigs = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop, '', 'test']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests now belong in utils.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved the tests to test/utils
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not move the whole file. It still contains many auth-specific tests.
.then((decodedIdToken: DecodedIdToken) => { | ||
decodedIdToken.uid = decodedIdToken.sub; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved the code to set the uid
alias here. Another way is to add helper functions, verifySessionCookie()
and verifyIdToken()
, to auth/token-verifier
. Let me know your thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about extending the util.TokenVerifier
as AuthTokenVerifier
, and add the logic there?
* @return {Promise<T>} A promise fulfilled with the decoded claims of the Firebase Auth ID | ||
* token. | ||
*/ | ||
public verifyJWT<T extends object>(jwtToken: string, isEmulator = false): Promise<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hiranya911 :
To make the type check tighter we can introduce a base type for returned tokens...
interface FirebaseToken {
// registered JWT claims
iss: string
aud: string
}
interface DecodedIdToken extends FirebaseToken {
...
}
public verifyJWT<T extends FirebaseToken>
Let me know your thoughts. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this looks good to me. But I don't think we need a generic at all. Just make auth extend the TokenVerifier class, and transform the FirebaseToken into DecodedIdToken there.
// in utils
interface DecodedToken {
iss: string;
aud: string;
sub: string;
}
// in auth
interface DecodedIdToken {
iss: string;
aud: string;
sub: string;
uid: string;
}
// at callsite
const decodedToken: DecodedToken = await tokenVerifier.verifyToken();
const result: DecodedIdToken = {...decodedToken, uid: decodedToken.sub};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets find a more typesafe way to handle the returns. Plus separation of tests between auth and utils.
.then((decodedIdToken: DecodedIdToken) => { | ||
decodedIdToken.uid = decodedIdToken.sub; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about extending the util.TokenVerifier
as AuthTokenVerifier
, and add the logic there?
} else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || | ||
!('code' in tokenInfo.expiredErrorCode)) { | ||
throw new Error('The JWT expiration error code must be a non-null ErrorInfo object.'); | ||
} else if (!(typeof tokenInfo.errorType === 'function' && tokenInfo.errorType !== null)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typeof errorType === 'function'
also asserts that errorType !== null
. So the 2nd part of the conjunction is redundant.
* token. | ||
*/ | ||
public verifyJWT(jwtToken: string, isEmulator = false): Promise<DecodedIdToken> { | ||
public verifyJWT<T extends object>(jwtToken: string, isEmulator = false): Promise<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking the type safety a bit. We should probably introduce a new interface for the return type of this class. And then convert it into an auth specific return type in auth/token-verifier
.
const decodedIdToken = (decodedToken as DecodedIdToken); | ||
decodedIdToken.uid = decodedIdToken.sub; | ||
resolve(decodedIdToken); | ||
resolve(decodedToken as T); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not particularly typesafe.
'invalidCredential' in tokenInfo.errorCodeConfig || | ||
'internalError' in tokenInfo.errorCodeConfig || | ||
'expiredError' in tokenInfo.errorCodeConfig)) { | ||
throw new Error('The provided error code config must be a non-null ErrorCodeInfo object.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ErrorCodeConfig?
@@ -30,10 +30,12 @@ import LegacyFirebaseTokenGenerator = require('firebase-token-generator'); | |||
import * as mocks from '../../resources/mocks'; | |||
import { FirebaseTokenGenerator, ServiceAccountSigner } from '../../../src/auth/token-generator'; | |||
import * as verifier from '../../../src/utils/token-verifier'; | |||
import * as verifierUtil from '../../../src/auth/token-verifier-util'; | |||
import { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I wasn't clear. But I didn't mean moving this entire file into utils. Some tests in this file are still very auth-specific, and therefore should be in auth. But the test cases that specifically test for the utils.token-verifier
, should be moved into a new test module (like the invalidErrorTypes
test cases).
I think a good rule of thumb is if a test requires an import from auth
, then it probably belongs in auth
.
Closing this as we decided to change the overall approach and design of the refactor. This work is replaced by #1204 |
FirebaseTokenVerifier
to utilFirebaseTokenInfo
to throw public error types fromFirebaseTokenVerifier