Skip to content

Commit

Permalink
refactor(token): token now has a createdAt field (#576)
Browse files Browse the repository at this point in the history
Closes #576
  • Loading branch information
alain-charles authored and nnixaa committed Jul 26, 2018
1 parent 42c588e commit 60bb79e
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 36 deletions.
12 changes: 7 additions & 5 deletions src/framework/auth/services/token/token-parceler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ import { NB_AUTH_TOKENS } from '../../auth.options';
describe('token-parceler', () => {

let tokenParceler: NbAuthTokenParceler;
const simpleToken = nbAuthCreateToken(NbAuthSimpleToken, 'test value', 'strategy');

const createdAt = new Date(1532350800000);
const simpleToken = nbAuthCreateToken(NbAuthSimpleToken, 'test value', 'strategy', createdAt);
// tslint:disable-next-line
const wrappedSimple = `{"name":"${NbAuthSimpleToken.NAME}","ownerStrategyName":"${simpleToken.getOwnerStrategyName()}","value":"${simpleToken.getValue()}"}`;
const wrappedSimple = `{"name":"${NbAuthSimpleToken.NAME}","ownerStrategyName":"${simpleToken.getOwnerStrategyName()}","createdAt":${simpleToken.getCreatedAt().getTime()},"value":"${simpleToken.getValue()}"}`;
// tslint:disable-next-line
const jwtToken = nbAuthCreateToken(NbAuthJWTToken, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjI1MTczMTQwNjYxNzUsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0=.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773', 'strategy');
const jwtToken = nbAuthCreateToken(NbAuthJWTToken, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjZXJlbWEuZnIiLCJpYXQiOjE1MzIzNTA4MDAsImV4cCI6MTUzMjQzNzIwMCwic3ViIjoiQWxhaW4gQ0hBUkxFUyIsImFkbWluIjp0cnVlfQ.iICwNqhvg9KPv3_MSg3HCydyAgAYI9mL3ZejLkY11Ck', 'strategy', createdAt);
// tslint:disable-next-line
const wrappedJWT = `{"name":"${NbAuthJWTToken.NAME}","ownerStrategyName":"${jwtToken.getOwnerStrategyName()}","value":"${jwtToken.getValue()}"}`;
const wrappedJWT = `{"name":"${NbAuthJWTToken.NAME}","ownerStrategyName":"${jwtToken.getOwnerStrategyName()}","createdAt":${jwtToken.getCreatedAt().getTime()},"value":"${jwtToken.getValue()}"}`;
// tslint:disable-next-line
const wrappedNonExisting = `{"name":"non-existing","value":"${simpleToken.getValue()}","ownerStrategyName":"${simpleToken.getOwnerStrategyName()}"}`;
const wrappedNonExisting = `{"name":"non-existing","value":"${simpleToken.getValue()}","ownerStrategyName":"${simpleToken.getOwnerStrategyName()}","createdAt":"${createdAt.getTime()}"}`;
const wrappedInvalid = `{"name":"non-existing"`;

describe('default configuration', () => {
Expand Down
8 changes: 7 additions & 1 deletion src/framework/auth/services/token/token-parceler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NB_AUTH_TOKENS } from '../../auth.options';
export interface NbTokenPack {
name: string,
ownerStrategyName: string,
createdAt: Number,
value: string,
}

Expand All @@ -25,6 +26,7 @@ export class NbAuthTokenParceler {
return JSON.stringify({
name: token.getName(),
ownerStrategyName: token.getOwnerStrategyName(),
createdAt: token.getCreatedAt().getTime(),
value: token.toString(),
});
}
Expand All @@ -33,14 +35,18 @@ export class NbAuthTokenParceler {
let tokenClass: NbAuthTokenClass = this.fallbackClass;
let tokenValue = '';
let tokenOwnerStrategyName = '';
let tokenCreatedAt: Date = null;

const tokenPack: NbTokenPack = this.parseTokenPack(value);
if (tokenPack) {
tokenClass = this.getClassByName(tokenPack.name) || this.fallbackClass;
tokenValue = tokenPack.value;
tokenOwnerStrategyName = tokenPack.ownerStrategyName;
tokenCreatedAt = new Date(Number(tokenPack.createdAt));
}

return nbAuthCreateToken(tokenClass, tokenValue, tokenOwnerStrategyName);
return nbAuthCreateToken(tokenClass, tokenValue, tokenOwnerStrategyName, tokenCreatedAt);

}

// TODO: this could be moved to a separate token registry
Expand Down
52 changes: 38 additions & 14 deletions src/framework/auth/services/token/token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@ import { NbAuthOAuth2Token, NbAuthJWTToken, NbAuthSimpleToken } from './token';

describe('auth token', () => {
describe('NbAuthJWTToken', () => {
const now = new Date();

// tslint:disable
const simpleToken = new NbAuthSimpleToken('token','strategy');
const validJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjI1MTczMTQwNjYxNzUsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0=.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773', '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 noExpJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJuYW1lIjoiQ2hyaXMgU2V2aWxsZWphIiwiYWRtaW4iOnRydWV9.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773','strategy');
const noIatJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjZXJlbWEuZnIiLCJleHAiOjE1MzI0MzcyMDAsInN1YiI6IkFsYWluIENIQVJMRVMiLCJhZG1pbiI6dHJ1ZX0.cfwQlKo6xomXkE-U-SOqse2GjdxncOuhdd1VWIOiYzA', 'strategy');

const noExpJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjZXJlbWEuZnIiLCJpYXQiOjE1MzIzNTA4MDAsInN1YiI6IkFsYWluIENIQVJMRVMiLCJhZG1pbiI6dHJ1ZX0.heHVXkHexwqbPCPUAvkJlXO6tvxzxTKf4iP0OWBbp7Y','strategy');

const expiredJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjEzMDA4MTkzODAsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773','strategy');

// tslint:enable

it('getPayload success', () => {
expect(validJWTToken.getPayload())
.toEqual(JSON.parse('{"iss":"scotch.io","exp":2517314066175,"name":"Chris Sevilleja","admin":true}'));
// 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', () => {
Expand All @@ -51,9 +57,25 @@ describe('auth token', () => {
`The token ${invalidBase64JWTToken.getValue()} is not valid JWT token and cannot be parsed.`));
});

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

it('getCreatedAt success : exp for validJWTToken', () => {
const date = new Date();
date.setTime(1532350800000)
expect(validJWTToken.getCreatedAt()).toEqual(date);
});

it('getCreatedAt success : now for noIatJWTToken', () => {
// we consider dates are the same if differing from minus than 10 ms
expect(noIatJWTToken.getCreatedAt().getTime() - now.getTime() < 10);
});

it('getTokenExpDate success', () => {
const date = new Date(0);
date.setUTCSeconds(2517314066175);
date.setTime(2532350800000);
expect(validJWTToken.getTokenExpDate()).toEqual(date);
});

Expand All @@ -71,7 +93,8 @@ describe('auth token', () => {

it('isValid fail', () => {
// without token
expect(new NbAuthJWTToken('', 'strategy').isValid()).toBeFalsy();
expect(new NbAuthJWTToken('', 'strategy', new Date()).isValid()).toBeFalsy();


// expired date
expect(expiredJWTToken.isValid()).toBeFalsy();
Expand All @@ -94,7 +117,8 @@ describe('auth token', () => {

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

it('NbAuthJWTToken name', () => {
Expand Down Expand Up @@ -123,7 +147,9 @@ describe('auth token', () => {
example_parameter: 'example_value',
};

const validToken = new NbAuthOAuth2Token(token, 'strategy');

let validToken = new NbAuthOAuth2Token(token, 'strategy');

const emptyToken = new NbAuthOAuth2Token({}, 'strategy');

const noExpToken = new NbAuthOAuth2Token({
Expand All @@ -144,14 +170,12 @@ describe('auth token', () => {
`Cannot extract payload from an empty token.`));
});

it('getTokenExpDate success', () => {
it('getExpDate success', () => {
// recreate it here if we want to be in the same second
validToken = new NbAuthOAuth2Token(token, 'strategy');
const date = new Date();
date.setUTCSeconds(date.getUTCSeconds() + 3600);
expect(validToken.getTokenExpDate().getFullYear()).toEqual(date.getFullYear());
expect(validToken.getTokenExpDate().getDate()).toEqual(date.getDate());
expect(validToken.getTokenExpDate().getMonth()).toEqual(date.getMonth());
expect(validToken.getTokenExpDate().getMinutes()).toEqual(date.getMinutes());
expect(validToken.getTokenExpDate().getSeconds()).toEqual(date.getSeconds());
date.setTime(date.getTime() + 3600 * 1000);
expect(validToken.getTokenExpDate().getTime() - date.getTime() < 10);
});

it('getTokenExpDate is empty', () => {
Expand Down
59 changes: 43 additions & 16 deletions src/framework/auth/services/token/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export abstract class NbAuthToken {
abstract getPayload(): string;
// the strategy name used to acquire this token (needed for refreshing token)
abstract getOwnerStrategyName(): string;
abstract getCreatedAt(): Date;
abstract toString(): string;

getName(): string {
Expand All @@ -19,13 +20,14 @@ export interface NbAuthRefreshableToken {

export interface NbAuthTokenClass {
NAME: string;
new (raw: any, ownerStrategyName: string): NbAuthToken;
new (raw: any, ownerStrategyName: string, createdAt: Date): NbAuthToken;
}

export function nbAuthCreateToken(tokenClass: NbAuthTokenClass,
token: any,
ownerStrategyName: string) {
return new tokenClass(token, ownerStrategyName);
ownerStrategyName: string,
createdAt?: Date) {
return new tokenClass(token, ownerStrategyName, createdAt);
}

/**
Expand All @@ -36,8 +38,23 @@ export class NbAuthSimpleToken extends NbAuthToken {
static NAME = 'nb:auth:simple:token';

constructor(protected readonly token: any,
protected readonly ownerStrategyName: string) {
protected readonly ownerStrategyName: string,
protected createdAt?: Date) {
super();
this.createdAt = this.prepareCreatedAt(createdAt);
}

protected prepareCreatedAt(date: Date) {
// For simple tokens, if not set the creation date is 'now'
return date ? date : new Date();
}

/**
* Returns the token's creation date
* @returns {Date}
*/
getCreatedAt(): Date {
return this.createdAt;
}

/**
Expand Down Expand Up @@ -80,6 +97,20 @@ export class NbAuthJWTToken extends NbAuthSimpleToken {

static NAME = 'nb:auth:jwt:token';

/**
* for JWT token, the iat (issued at) field of the token payload contains the creation Date
*/
protected prepareCreatedAt(date: Date) {
date = super.prepareCreatedAt(date);
let decoded = null;
try { // needed as getPayload() throws error and we want the token to be created in any case
decoded = this.getPayload();
}
finally {
return decoded && decoded.iat ? new Date(Number(decoded.iat) * 1000) : date;
}
}

/**
* Returns payload object
* @returns any
Expand Down Expand Up @@ -119,10 +150,8 @@ export class NbAuthJWTToken extends NbAuthSimpleToken {
if (!decoded.hasOwnProperty('exp')) {
return null;
}

const date = new Date(0);
date.setUTCSeconds(decoded.exp);

date.setUTCSeconds(decoded.exp); // 'cause jwt token are set in seconds
return date;
}

Expand Down Expand Up @@ -151,10 +180,12 @@ export class NbAuthOAuth2Token extends NbAuthSimpleToken {

static NAME = 'nb:auth:oauth2:token';

constructor(protected data: { [key: string]: string|number }|string = {},
protected ownerStrategyName: string) {
constructor( data: { [key: string]: string|number }|string = {},
ownerStrategyName: string,
createdAt?: Date) {

// we may get it as string when retrieving from a storage
super(prepareOAuth2Token(data), ownerStrategyName);
super(prepareOAuth2Token(data), ownerStrategyName, createdAt);
}

/**
Expand Down Expand Up @@ -209,12 +240,8 @@ export class NbAuthOAuth2Token extends NbAuthSimpleToken {
if (!this.token.hasOwnProperty('expires_in')) {
return null;
}

const date = new Date();
date.setUTCSeconds(new Date().getUTCSeconds() + Number(this.token.expires_in));

return date;
}
return new Date(this.createdAt.getTime() + Number(this.token.expires_in) * 1000);
}

/**
* Convert to string
Expand Down

0 comments on commit 60bb79e

Please sign in to comment.