-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Feat/auth : Rejects malformed JWTToken and requireValidToken Option #597
Feat/auth : Rejects malformed JWTToken and requireValidToken Option #597
Conversation
Now : If existing, NbAuthResult contains backend error description other Changes requested by Dmitry (first review)
Now : If existing, NbAuthResult contains backend error description other Changes requested by Dmitry (first review) +tslint missing trailing comma arghhh
…strategy used to create the token (future use)
The token now contains ownerStrategyName, with is a back link to the strategy used to create the token (future use). BREAKING CHANGE : NbAuthCreateToken (token.ts) now takes a third parameter, which is the ownerStrategyName Tokens are created by strategies that are called from services, so it is *potentially* a breaking change.
The token now contains ownerStrategyName, with is a back link to the strategy used to create the token (future use). updated unit tests files and oauth2-password-login.component (breaking change below) BREAKING CHANGE : NbAuthCreateToken (token.ts) now takes a third parameter, which is the ownerStrategyName Tokens are created by strategies that are called from services, so it is *potentially* a breaking change.
removed useless code and cleaned one unit test file BREAKING CHANGE : NbAuthCreateToken (token.ts) now takes a third parameter, which is the ownerStrategyName Tokens are created by strategies that are called from services, so it is *potentially* a breaking change.
The framework now accepts empty jwt token (it is the NbPasswordAuthStrategy failWhenNoToken oprion) but it rejects every non empty malformed JWT tokens A new option requireValidToken has been added for accepting or rejecting inValid (!isValid) tokend. Option set to false by default so no breaking changes cleaned code Resolves the #517 issue
…eValidtoken is now present for all strategies. Factorized code in password strategy for error response handling Updated unit tests No breaking change since : - requireValidToken is set to false by default - failWhenNoToken was not released.
…ValidToken # Conflicts: # src/framework/auth/strategies/auth-strategy.ts
@nnixaa the last commit resolves merging conflict and implements failWhenNoToken removal. |
Codecov Report
@@ Coverage Diff @@
## master #597 +/- ##
==========================================
- Coverage 72.71% 72.49% -0.23%
==========================================
Files 153 153
Lines 4222 4264 +42
Branches 323 330 +7
==========================================
+ Hits 3070 3091 +21
- Misses 1095 1109 +14
- Partials 57 64 +7
|
export class InvalidJWTTokenError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
const actualPrototype = new.target.prototype; |
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.
no need in additional variable here, just Object.setPrototypeOf(this, new.target.prototype);
@@ -14,6 +14,14 @@ export abstract class NbAuthToken { | |||
} | |||
} | |||
|
|||
export class InvalidJWTTokenError extends Error { |
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.
InvalidJWTTokenError
-> NbAuthInvalidJWTTokenError
would be better
Object.setPrototypeOf(this, actualPrototype); | ||
} | ||
} | ||
|
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.
we also need another error then, as we cannot use InvalidJWTTokenError
everywhere, since not every token is JWT token.
probably like this
NbAuthInvalidTokenError
NbAuthInvalidJWTTokenError
@@ -221,7 +237,7 @@ export class NbAuthOAuth2Token extends NbAuthSimpleToken { | |||
*/ | |||
getPayload(): any { | |||
if (!this.token || !Object.keys(this.token).length) { | |||
throw new Error('Cannot extract payload from an empty token.'); | |||
throw new InvalidJWTTokenError('Cannot extract payload from an empty token.'); |
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 isn't a JWT token error I suppose as we are in NbAuthOAuth2Token
const requireValidToken: boolean = this.getOption('requireValidToken'); | ||
const token = nbAuthCreateToken<T>(this.getOption('token.class'), value, this.getName()); | ||
if (requireValidToken && !token.isValid()) { | ||
throw new InvalidJWTTokenError('Token is empty or invalid.'); |
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.
same here, we can only throw a more generic error here
@@ -7,7 +7,6 @@ import { NbAuthStrategyOptions } from '../auth-strategy-options'; | |||
import { NbAuthSimpleToken } from '../../services/'; | |||
|
|||
export class NbDummyAuthStrategyOptions extends NbAuthStrategyOptions { | |||
name: string; |
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.
why do we need to remove this name?
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.
@nnixaa
As for the moment requireValidToken
was set at strategy level, i put it in the ancestor NbAuthStrategyOption
.
I then realized that only NbPasswordStrategyOption
was extending NbAuthStrategyOption
.
I guess it would be better (even if requireValidToken
finally goes at endpoint level) to make OAuth2 and Dummy options also extend NbAuthStrategyOption
.
In this case name
(and probably more attributes) can be declared in NbAuthStrategyOption
.
What do you think ?
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.
@alain-charles I'm bit confused, as far as I can see, all strategies' options (oauth2, dummy, password) are extended from NbAuthStrategyOptions
. Am I missing something?
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.
@nnixaa Have a look at NbOAuth2AuthStrategyOptions
in the master branch
src/playground/auth/auth.module.ts
Outdated
@@ -74,7 +74,7 @@ import { NbAuthGuard } from './auth-guard.service'; | |||
|
|||
NbPasswordAuthStrategy.setup({ | |||
name: 'email', | |||
|
|||
requireValidToken: false, |
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.
I would say we need to introduce this not on a strategy level, but on an endpoint level.
For instance, in a case when login endpoint requires to have a valid token, but register endpoint doesn't.
@@ -16,13 +16,11 @@ import { | |||
} from '@nebular/theme'; | |||
|
|||
import { | |||
NbAuthModule, | |||
NbAuthModule, NbAuthOAuth2JWTToken, |
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.
I guess this change should not be a part of this PR?
@nnixaa i'm still working on this.
Do you still agree with that, i.e. even if requireValidToken is set to false on the endpoint, we reject the token if it is malformed ? I still think we shoud reject malformed JWT tokens, whatever the value of What do you think ? |
@alain-charles yes, let's do this way. But then we need to parse the token at the creation time I guess, not at the |
@nnixaa it is already the case in what i've written and i'll push |
@alain-charles yes, I agree, but since we parse it here already, moreover we have to parse it (to validate and fail) there is no need to parse it all over again on each |
@nnixaa concretely you wanna store the decoded payload as a token attribute ? |
@alain-charles yes, something like this:
and then the
Does it make sense? |
if you want present and valid token, use requireValidToken = true at the endpoint level if jwt malformed, token are rejected.
…o feat/auth_requireValidToken
@nnixaa finally i suuceeded in pushing something that makes sense. |
@alain-charles awesome, checking |
let errors = []; | ||
if (res instanceof HttpErrorResponse) { | ||
errors = this.getOption('errors.getter')(module, res, this.options); | ||
} else if (res instanceof NbAuthIllegalTokenError) { |
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.
so in case when the token is empty any of our token classes will throw the NbAuthTokenNotFoundError
exception, right? Which won't be caught by this if
and will go to something went wrong
part?
In this case the statement It MAY be created even if backend did not return any token, in this case, it is !Valid
is not valid and empty
token will never be created.
If my reasoning is correct, then we just need to decide, what empty
means - malformed or invalid.
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.
@nnixaa here is what i have coded (or what i think i've coded 🤣 ) :
NbAuthTokenNotFoundError
is thrown if we did not find the token key in the response payload (i.e. noaccess_token
key in OAuth2 token response, notoken
key in simple token response...). That is what i call anempty
token.
=> the error is not catched during token construction, because we want to allow some endpoints to create such tokens ifrequireValidToken
is false.
=> But after token construction, ifrequireValidToken
is set to true, then the isValid() will return false so that thecreateToken()
method will throw anNbInvalidTokenError
saying the token is empty or not valid.NbInvalidTokenError
will then be catched by the strategy that will display the error message.
if (failWhenInvalidToken && !token.isValid()) {
// If we require a valid token (i.e. isValid), then we MUST throw NbAuthIllegalTokenError so that the strategies
// intercept it
throw new NbAuthIllegalTokenError('Token is empty or invalid.');
}
- If we find something in the token key in the response payload (i.e
access_token
is present,token
is present), then i consider that
1/ we have a token (even if it is''
or{}
)
2/ the token we have must respect some rules (JWT format for instance) and if not we throw NbIllegalTokenError that will be catched in every case by the strategies.
To conclude
empty
means the key was not found in the response.- And
empty
tokens are never valid (isValid()
) but can be created ifrequireValidToken
is set to false - If the key is found, the token is not
empty
even if its value is''
or{}
and then it must pass the "wellFormed" control otherwise it is declared Illegal (NbIllegalTokenError
) and rejected.
Hope i am clear.
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.
I think we can decide that ''
or {}
are empty tokens....
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.
Ah okay, so we catch the empty token error during this check, right?
try {
this.parsePayload();
} catch (err) {
if (err instanceof NbAuthIllegalTokenError) { // token is present but illegal (empty or malformed), we reject it
throw err;
}
}
so if it is illegal - throw next, and if it's just invalid (empty) - do nothing, right?
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.
Exactly!
If empty the constructor does not throw any error .
Then the createToken() method (that launched the constructor) :
- do nothing if requireValidToken is false so that we have an empty token created, stored and the login/register/refresh succeeds
- throw NbIllegalTokenError (we could create NbRequireValidTokenError) if requireValidToken is true so that stratégies can return the message in NBAuthResult and login/refresh/register fail with the message
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.
Got it, this is cool, but I'd suggest we inverse this check so that we don't block any other possible exception. So we specifically block the NbAuthTokenNotFoundError
and don't touche other expectations including NbAuthIllegalTokenError
:
try {
this.parsePayload();
} catch (err) {
if (!(err instanceof NbAuthTokenNotFoundError)) {
throw err;
}
}
Or, another option is to not throw the NbAuthTokenNotFoundError
expectation at all, just create the token.
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.
Yes I agree .
I’ m gonna push very soon
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.
@nnixaa What do you think now and what else can we do to be ready ?
…o feat/auth_requireValidToken
Short description
The framework now :
requireValidToken
,requireValidToken
at the endpoint level that is set tofalse
by default,requireValidToken
is set tofalse
(rejects otherwise)!isValid()
token ifrequireValidToken
is set to true,important
requireValidToken
option is set to false by default so that there is no breaking changes.failWhenNoToken
has been removed from password strategy as it was still not released and becoming redundantissue resolved
Resolves the #517 issue