Skip to content
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

Merged
merged 72 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
cef7a1d
Added password grant_type option
alain-charles Jun 29, 2018
9f0dbab
Added password grant_type option implementation
alain-charles Jun 29, 2018
44d7d2e
Playground sample for oAuth2 password grant-type
alain-charles Jun 29, 2018
f57b737
Added route to oAuth2 password grant-type sample
alain-charles Jun 29, 2018
ed4628e
Addes angular-jwt for decoding jwt token (used in playground only)
alain-charles Jun 29, 2018
d7f01df
Added /api/auth/token endpoint (oAuth2 password grant-type playground)
alain-charles Jun 29, 2018
af52e88
Patched code for passing ci
alain-charles Jun 29, 2018
554f7dc
code optimization for passing ci
alain-charles Jun 29, 2018
fa0548f
Merge branch 'master' into master
nnixaa Jun 29, 2018
b72adae
Changes request by Dmitry (first review)
alain-charles Jun 29, 2018
733efc0
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
57fab69
Merge remote-tracking branch 'origin/master'
alain-charles Jun 29, 2018
c50df3a
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
dda5436
Added unit tests for oAuth-strategy password authentication
alain-charles Jul 2, 2018
ef0be25
Merge branch 'master' into master
alain-charles Jul 3, 2018
e180dc3
Merge branch 'master' into master
nnixaa Jul 4, 2018
6aa1fdb
Cleaned code according to nnixaa second review
alain-charles Jul 4, 2018
96c7344
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 4, 2018
cf82c42
Removed package-lock.json from git repo
alain-charles Jul 4, 2018
dcff930
package-lock.json to revert
alain-charles Jul 4, 2018
315662f
reverted package-lock.json
alain-charles Jul 4, 2018
57a0230
Added new grant_type 'PASSWORD' in the block comment for it to be ins…
alain-charles Jul 4, 2018
d69d633
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 5, 2018
1be8d91
Add the refreshtoken request management in NbJwtInterceptor and NbAut…
alain-charles Jul 13, 2018
4b67825
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 19, 2018
deea3e2
Merge branch 'master' into temp
alain-charles Jul 19, 2018
acd0241
The token now contains ownerStrategyName, with is a back link to the …
alain-charles Jul 19, 2018
25250f0
feature/auth:
alain-charles Jul 19, 2018
c0fdd31
feature/auth:
alain-charles Jul 19, 2018
bd564b3
feature/auth:
alain-charles Jul 20, 2018
44ed61a
Merge branch 'master' into master
nnixaa Jul 20, 2018
9c16387
feature/auth
alain-charles Jul 24, 2018
2dd26c4
Merge remote-tracking branch 'origin/master'
alain-charles Jul 24, 2018
b284377
Merge branch 'master' into master
alain-charles Jul 24, 2018
e462c7d
feature/auth
alain-charles Jul 24, 2018
a3ee10b
Merge branch 'master' into master
nnixaa Jul 24, 2018
b38ae55
Merge branch 'master' into master
nnixaa Jul 25, 2018
62a2aff
feature/auth
alain-charles Jul 25, 2018
66ca493
Merge remote-tracking branch 'origin/master'
alain-charles Jul 25, 2018
3bd5a50
feature/auth
alain-charles Jul 25, 2018
398c058
feature/auth
alain-charles Jul 25, 2018
ee706d3
Merge branch 'master' into master
alain-charles Jul 26, 2018
5eb5821
feature/auth
alain-charles Jul 26, 2018
1a08225
Merge remote-tracking branch 'origin/master'
alain-charles Jul 26, 2018
1d697bc
feature/auth
alain-charles Jul 27, 2018
1c25956
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
ff0e8d3
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
e7af99e
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
95632b1
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
bc4001f
Merge branch 'master' into master
nnixaa Jul 30, 2018
e06a45c
Feat/auth
alain-charles Jul 31, 2018
7c6fc43
Merge remote-tracking branch 'upstream/master' into feat/auth-Automat…
alain-charles Jul 31, 2018
88c7106
Moved code from tokenservice to strategy.
alain-charles Jul 31, 2018
76dda02
Added setRefreshToken(token)
alain-charles Jul 31, 2018
a75f75b
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 1, 2018
ab75598
feat/auth
alain-charles Aug 2, 2018
2ad04ba
feat/auth
alain-charles Aug 2, 2018
cfe6b87
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 2, 2018
ad90f20
feat/auth
alain-charles Aug 2, 2018
66cf701
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 7, 2018
9d35278
Removed not released failWhenNoToken from password strategy as requir…
alain-charles Aug 7, 2018
432b2cc
Corrected import paths
alain-charles Aug 7, 2018
01e2c2f
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 7, 2018
a22d3ef
Merge branch 'master' into feat/auth_requireValidToken
nnixaa Aug 7, 2018
ffef510
Merge branch 'master' into feat/auth_requireValidToken
nnixaa Aug 8, 2018
03e1fae
Payload(s) are now parsed during token construction
alain-charles Aug 9, 2018
cb1bfed
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 9, 2018
b6a9db6
Merge remote-tracking branch 'origin/feat/auth_requireValidToken' int…
alain-charles Aug 9, 2018
4b57287
Inverted error test in parsePayload
alain-charles Aug 10, 2018
27d74a7
Merge branch 'master' into feat/auth_requireValidToken
nnixaa Aug 13, 2018
9a8d41f
Merge remote-tracking branch 'upstream/master' into feat/auth_require…
alain-charles Aug 14, 2018
ae46531
Merge remote-tracking branch 'origin/feat/auth_requireValidToken' int…
alain-charles Aug 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions src/framework/auth/services/token/token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ describe('auth token', () => {
// tslint:disable
const simpleToken = new NbAuthSimpleToken('token','strategy');
const validJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjZXJlbWEuZnIiLCJpYXQiOjE1MzIzNTA4MDAsImV4cCI6MjUzMjM1MDgwMCwic3ViIjoiQWxhaW4gQ0hBUkxFUyIsImFkbWluIjp0cnVlfQ.Rgkgb4KvxY2wp2niXIyLJNJeapFp9z3tCF-zK6Omc8c', 'strategy');
const emptyJWTToken = new NbAuthJWTToken('..', 'strategy');
const invalidBase64JWTToken = new NbAuthJWTToken('h%2BHY.h%2BHY.h%2BHY','strategy');

const invalidJWTToken = new NbAuthJWTToken('.','strategy');

const noIatJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjZXJlbWEuZnIiLCJleHAiOjE1MzI0MzcyMDAsInN1YiI6IkFsYWluIENIQVJMRVMiLCJhZG1pbiI6dHJ1ZX0.cfwQlKo6xomXkE-U-SOqse2GjdxncOuhdd1VWIOiYzA', 'strategy');

Expand All @@ -27,36 +23,40 @@ describe('auth token', () => {

// tslint:enable

it('getPayload success', () => {
expect(validJWTToken.getPayload())
// tslint:disable-next-line
.toEqual(JSON.parse('{"iss":"cerema.fr","iat":1532350800,"exp":2532350800,"sub":"Alain CHARLES","admin":true}'));
});

it('getPayload, not valid JWT token, must consist of three parts', () => {
it('JWT Token getPayload(), not valid JWT token, must consist of three parts', () => {
const token = new NbAuthJWTToken('.', 'strategy');
expect(() => {
invalidJWTToken.getPayload();
token.getPayload()
})
.toThrow(new Error(
`The payload ${invalidJWTToken.getValue()} is not valid JWT payload and must consist of three parts.`));
`The payload . is not valid JWT payload and must consist of three parts.`));
});

it('getPayload, not valid JWT token, cannot be decoded', () => {
it('JWT Token getPayload(),, not valid JWT token, cannot be decoded', () => {
const token = new NbAuthJWTToken('..', 'strategy');
expect(() => {
emptyJWTToken.getPayload();
token.getPayload()
})
.toThrow(new Error(
`The payload ${emptyJWTToken.getValue()} is not valid JWT payload and cannot be decoded.`));
`The payload .. is not valid JWT payload and cannot be decoded.`));
});

it('getPayload, not valid base64 in JWT token, cannot be decoded', () => {
const token = new NbAuthJWTToken('h%2BHY.h%2BHY.h%2BHY', 'strategy');
expect(() => {
invalidBase64JWTToken.getPayload();
token.getPayload()
})
.toThrow(new Error(
`The payload ${invalidBase64JWTToken.getValue()} is not valid JWT payload and cannot be parsed.`));
`The payload h%2BHY.h%2BHY.h%2BHY is not valid JWT payload and cannot be parsed.`));
});

it('getPayload success', () => {
expect(validJWTToken.getPayload())
// tslint:disable-next-line
.toEqual(JSON.parse('{"iss":"cerema.fr","iat":1532350800,"exp":2532350800,"sub":"Alain CHARLES","admin":true}'));
});


it('getCreatedAt success : now for simpleToken', () => {
// we consider dates are the same if differing from minus than 10 ms
expect(simpleToken.getCreatedAt().getTime() - now.getTime() < 10);
Expand Down
47 changes: 34 additions & 13 deletions src/framework/auth/services/token/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export abstract class NbAuthToken {
}
}

export class InvalidJWTTokenError extends Error {
Copy link
Collaborator

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

constructor(message: string) {
super(message);
const actualPrototype = new.target.prototype;
Copy link
Collaborator

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);

Object.setPrototypeOf(this, actualPrototype);
}
}

Copy link
Collaborator

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

export interface NbAuthRefreshableToken {
getRefreshToken(): string;
setRefreshToken(refreshToken: string);
Expand All @@ -34,24 +42,27 @@ export function nbAuthCreateToken<T extends NbAuthToken>(tokenClass: NbAuthToken
export function decodeJwtPayload(payload: string): string {

if (!payload) {
throw new Error('Cannot extract payload from an empty token.');
throw new InvalidJWTTokenError('Cannot extract from an empty payload.');
}

const parts = payload.split('.');

if (parts.length !== 3) {
throw new Error(`The payload ${payload} is not valid JWT payload and must consist of three parts.`);
throw new InvalidJWTTokenError(
`The payload ${payload} is not valid JWT payload and must consist of three parts.`);
}

let decoded;
try {
decoded = urlBase64Decode(parts[1]);
} catch (e) {
throw new Error(`The payload ${payload} is not valid JWT payload and cannot be parsed.`);
throw new InvalidJWTTokenError(
`The payload ${payload} is not valid JWT payload and cannot be parsed.`);
}

if (!decoded) {
throw new Error(`The payload ${payload} is not valid JWT payload and cannot be decoded.`);
throw new InvalidJWTTokenError(
`The payload ${payload} is not valid JWT payload and cannot be decoded.`);
}

return JSON.parse(decoded);
Expand Down Expand Up @@ -127,12 +138,17 @@ export class NbAuthJWTToken extends NbAuthSimpleToken {
* for JWT token, the iat (issued at) field of the token payload contains the creation Date
*/
protected prepareCreatedAt(date: Date) {
let decoded;
// We do not want prepareCreatedAt to fail if token is empty because we have to accept it
// if requireValidToken is set to false
// We want it to fail only if payload is present but malformed.
try {
decoded = this.getPayload();
}
finally {
const decoded = this.getPayload();
return decoded && decoded.iat ? new Date(Number(decoded.iat) * 1000) : super.prepareCreatedAt(date);
} catch (err) {
if (err instanceof InvalidJWTTokenError) {
return super.prepareCreatedAt(date);
}
throw err;
}
}

Expand Down Expand Up @@ -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.');
Copy link
Collaborator

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

}

return this.token;
Expand Down Expand Up @@ -274,12 +290,17 @@ export class NbAuthOAuth2JWTToken extends NbAuthOAuth2Token {
* for Oauth2 JWT token, the iat (issued at) field of the access_token payload
*/
protected prepareCreatedAt(date: Date) {
let decoded;
// We do not want prepareCreatedAt to fail if token is empty because we have to accept it
// if requireValidToken is set to false
// We want it to fail is payload is present but malformed.
try {
decoded = this.getAccessTokenPayload();
}
finally {
const decoded = this.getAccessTokenPayload();
return decoded && decoded.iat ? new Date(Number(decoded.iat) * 1000) : super.prepareCreatedAt(date);
} catch (err) {
if (err instanceof InvalidJWTTokenError) {
return super.prepareCreatedAt(date);
}
throw err;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/framework/auth/strategies/auth-strategy-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { NbAuthTokenClass } from '../services/';

export class NbAuthStrategyOptions {
name: string;
requireValidToken?: boolean = false;
token?: {
class?: NbAuthTokenClass;
[key: string]: any;
Expand Down
9 changes: 7 additions & 2 deletions src/framework/auth/strategies/auth-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
import { NbAuthResult } from '../services/auth-result';
import { NbAuthStrategyOptions } from './auth-strategy-options';
import { deepExtend, getDeepFromObject } from '../helpers';
import { NbAuthToken, nbAuthCreateToken } from '../services/token/token';
import { NbAuthToken, nbAuthCreateToken, InvalidJWTTokenError } from '../services/token/token';

export abstract class NbAuthStrategy {

Expand All @@ -21,7 +21,12 @@ export abstract class NbAuthStrategy {
}

createToken<T extends NbAuthToken>(value: any): T {
return nbAuthCreateToken<T>(this.getOption('token.class'), value, this.getName());
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.');
Copy link
Collaborator

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

}
return token;
}

getName(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { NbAuthStrategyOptions } from '../auth-strategy-options';
import { NbAuthSimpleToken } from '../../services/';

export class NbDummyAuthStrategyOptions extends NbAuthStrategyOptions {
name: string;
Copy link
Collaborator

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?

Copy link
Contributor Author

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 ?

Copy link
Collaborator

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?

Copy link
Contributor Author

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

token? = {
class: NbAuthSimpleToken,
};
Expand Down
12 changes: 12 additions & 0 deletions src/framework/auth/strategies/dummy/dummy-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class NbDummyAuthStrategy extends NbAuthStrategy {
}

protected createDummyResult(data?: any): NbAuthResult {

if (this.getOption('alwaysFail')) {
return new NbAuthResult(
false,
Expand All @@ -87,6 +88,17 @@ export class NbDummyAuthStrategy extends NbAuthStrategy {
);
}

try {
this.createToken('test token')
} catch (err) {
return new NbAuthResult(
false,
this.createFailResponse(data),
null,
[err.message],
);
}

return new NbAuthResult(
true,
this.createSuccessResponse(data),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { NbAuthOAuth2Token, NbAuthTokenClass } from '../../services';
import { NbAuthStrategyOptions } from '../auth-strategy-options';

export enum NbOAuth2ResponseType {
CODE = 'code',
Expand All @@ -24,8 +25,7 @@ export enum NbOAuth2ClientAuthMethod {
REQUEST_BODY = 'request-body',
}

export class NbOAuth2AuthStrategyOptions {
name: string;
export class NbOAuth2AuthStrategyOptions extends NbAuthStrategyOptions {
baseEndpoint?: string = '';
clientId: string = '';
clientSecret?: string = '';
Expand Down
24 changes: 21 additions & 3 deletions src/framework/auth/strategies/oauth2/oauth2-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
NbOAuth2GrantType, NbOAuth2ClientAuthMethod,
} from './oauth2-strategy.options';
import { NbAuthStrategyClass } from '../../auth.options';
import { InvalidJWTTokenError } from '../../services/';


/**
Expand Down Expand Up @@ -134,7 +135,6 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
this.getOption('defaultMessages'),
this.createToken(params));
}

return new NbAuthResult(
false,
params,
Expand All @@ -143,6 +143,21 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
[],
);
}),
catchError(err => { // createToken sent an MalformedJWTTokenError
const errors = [];
if (err instanceof InvalidJWTTokenError) {
errors.push(err.message)
} else {
errors.push('Something went wrong.');
}
return observableOf(
new NbAuthResult(
false,
err,
this.getOption('redirect.failure'),
errors,
));
}),
);
},
};
Expand Down Expand Up @@ -335,9 +350,12 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
} else {
errors = this.getOption('defaultErrors');
}
} else if (res instanceof InvalidJWTTokenError ) {
errors.push(res.message)
} else {
errors.push('Something went wrong.');
}
errors.push('Something went wrong.')
};

return observableOf(
new NbAuthResult(
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export interface NbPasswordStrategyMessage {
}

export class NbPasswordAuthStrategyOptions extends NbAuthStrategyOptions {
name: string;
baseEndpoint? = '/api/auth/';
login?: boolean | NbPasswordStrategyModule = {
alwaysFail: false,
Expand Down
Loading