Permalink
Browse files

feat(project): store the complete response in storage. AuthService.ge…

…tTimeLeft() added

BREAKING: accessTokenStorage renamed to storageKey and used as storage key for the complete response
BREAKING: internal works of Authentication refactored for performance. Some methods might be renamed or be removed
  • Loading branch information...
doktordirk committed Apr 12, 2016
1 parent 479e338 commit b98d839e93bc5a8d125b61175713d0e0f2459052
Showing with 315 additions and 241 deletions.
  1. +24 −14 src/authService.js
  2. +94 −82 src/authentication.js
  3. +4 −7 src/baseConfig.js
  4. +29 −29 test/authService.spec.js
  5. +163 −108 test/authentication.spec.js
  6. +1 −1 test/fetchClientConfig.spec.js
@@ -70,7 +70,7 @@ export class AuthService {
*
*/
getAccessToken() {
return this.authentication.accessToken;
return this.authentication.getAccessToken();
}
getCurrentToken() {
@@ -85,7 +85,7 @@ export class AuthService {
*
*/
getRefreshToken() {
return this.authentication.refreshToken;
return this.authentication.getRefreshToken();
}
/**
@@ -101,8 +101,8 @@ export class AuthService {
// auto-update token?
if (!authenticated
&& this.config.autoUpdateToken
&& this.authentication.accessToken
&& this.authentication.refreshToken) {
&& this.authentication.getAccessToken()
&& this.authentication.getRefreshToken()) {
authenticated = this.updateToken();
}
@@ -119,10 +119,20 @@ export class AuthService {
return authenticated;
}
/**
* Gets remaining time in seconds
*
* @returns {Number} remaing time for JWT tokens, NaN for all other tokesn
*
*/
getTimeLeft() {
return this.authentication.getTimeLeft();
}
/**
* Gets exp from token payload and compares to current time
*
* @returns {Boolean | undefined} undefined: Non-JWT payload, true: unexpired JWT tokens, false: else
* @returns {Boolean} getTimeLeft>0 time for JWT tokens, undefined other tokesn
*
*/
isTokenExpired() {
@@ -136,7 +146,7 @@ export class AuthService {
*
*/
getTokenPayload() {
return this.authentication.getTokenPayload();
return this.authentication.getPayload();
}
/**
@@ -146,24 +156,24 @@ export class AuthService {
*
*/
updateToken() {
if (!this.authentication.refreshToken) {
if (!this.authentication.getRefreshToken()) {
return Promise.reject(new Error('refreshToken not set'));
}
if (this.authentication.updateTokenCallstack.length === 0) {
const content = {
grant_type: 'refresh_token',
refresh_token: this.authentication.refreshToken,
refresh_token: this.authentication.getRefreshToken(),
client_id: this.config.clientId ? this.config.clientId : undefined
};
this.client.post(this.config.withBase(this.config.loginUrl), content)
.then(response => {
this.authentication.setTokensFromResponse(response);
this.authentication.responseObject = response;
this.authentication.resolveUpdateTokenCallstack(response);
})
.catch(err => {
this.authentication.removeTokens();
this.authentication.responseObject = null;
this.authentication.resolveUpdateTokenCallstack(Promise.reject(err));
});
}
@@ -201,7 +211,7 @@ export class AuthService {
return this.client.post(this.config.withBase(this.config.signupUrl), data)
.then(response => {
if (this.config.loginOnSignup) {
this.authentication.setTokensFromResponse(response);
this.authentication.responseObject = response;
}
this.authentication.redirect(redirectUri, this.config.signupRedirect);
@@ -237,7 +247,7 @@ export class AuthService {
return this.client.post(this.config.withBase(this.config.loginUrl), data)
.then(response => {
this.authentication.setTokensFromResponse(response);
this.authentication.responseObject = response;
this.authentication.redirect(redirectUri, this.config.loginRedirect);
@@ -255,7 +265,7 @@ export class AuthService {
*/
logout(redirectUri) {
return new Promise(resolve => {
this.authentication.removeTokens();
this.authentication.responseObject = null;
this.authentication.redirect(redirectUri, this.config.logoutRedirect);
@@ -283,7 +293,7 @@ export class AuthService {
authenticate(name, redirectUri, userData = {}) {
return this.authentication.authenticate(name, userData)
.then(response => {
this.authentication.setTokensFromResponse(response);
this.authentication.responseObject = response;
this.authentication.redirect(redirectUri, this.config.loginRedirect);
@@ -13,8 +13,16 @@ export class Authentication {
this.oAuth1 = oAuth1;
this.oAuth2 = oAuth2;
this.updateTokenCallstack = [];
this.accessToken = null;
this.refreshToken = null;
this.payload = null;
this.exp = null;
this.hasDataStored = false;
}
/* deprecated methods */
getLoginRoute() {
console.warn('DEPRECATED: Authentication.getLoginRoute. Use baseConfig.loginRoute instead.');
return this.config.loginRoute;
@@ -41,88 +49,119 @@ export class Authentication {
}
getToken() {
console.warn('DEPRECATED: Authentication.getToken. Use .accessToken instead.');
console.warn('DEPRECATED: Authentication.getToken. Use .getAccessToken() instead.');
return this.getAccessToken();
}
/* getters/setters for responseObject */
get responseObject() {
return JSON.parse(this.storage.get(this.config.storageKey));
}
set responseObject(response) {
if (response) {
this.getDataFromResponse(response);
return this.storage.set(this.config.storageKey, JSON.stringify(response));
}
this.deleteData();
return this.storage.remove(this.config.storageKey);
}
/* get data, update if needed first */
getAccessToken() {
if (!this.hasDataStored) this.getDataFromResponse(this.responseObject);
return this.accessToken;
}
getRefreshToken() {
console.warn('DEPRECATED: Authentication.getRefreshToken. Use .refreshToken instead.');
if (!this.hasDataStored) this.getDataFromResponse(this.responseObject);
return this.refreshToken;
}
/* getters/setters for tokens */
get accessToken() {
return this.storage.get(this.config.accessTokenStorage);
getPayload() {
if (!this.hasDataStored) this.getDataFromResponse(this.responseObject);
return this.payload;
}
set accessToken(newToken) {
if (newToken) {
return this.storage.set(this.config.accessTokenStorage, newToken);
}
return this.storage.remove(this.config.accessTokenStorage);
getExp() {
if (!this.hasDataStored) this.getDataFromResponse(this.responseObject);
return this.exp;
}
get refreshToken() {
return this.storage.get(this.config.refreshTokenStorage);
/* get status from data */
getTimeLeft() {
const exp = this.getExp();
return Number.isNaN(exp) ? NaN : exp - Math.round(new Date().getTime() / 1000);
}
set refreshToken(newToken) {
if (newToken) {
return this.storage.set(this.config.refreshTokenStorage, newToken);
}
return this.storage.remove(this.config.refreshTokenStorage);
isTokenExpired() {
const timeLeft = this.getTimeLeft();
return Number.isNaN(timeLeft) ? undefined : timeLeft < 0;
}
isAuthenticated() {
const isTokenExpired = this.isTokenExpired();
if (isTokenExpired === undefined ) return this.accessToken ? true : false;
return !isTokenExpired;
}
/* work with the token */
getPayload() {
console.warn('DEPRECATED: Authentication.getPayload(). Use .getTokenPayload() instead.');
return this.getTokenPayload();
}
/* get and set from response */
getDataFromResponse(response) {
const config = this.config;
this.accessToken = this.getTokenFromResponse(response, config.accessTokenProp, config.accessTokenName, config.accessTokenRoot);
getTokenPayload() {
const accessToken = this.accessToken;
if (accessToken && accessToken.split('.').length === 3) {
this.refreshToken = null;
if (config.useRefreshToken) {
try {
const base64Url = this.accessToken.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(decodeURIComponent(escape(window.atob(base64))));
this.refreshToken = this.getTokenFromResponse(response, config.refreshTokenProp, config.refreshTokenName, config.refreshTokenRoot);
} catch (e) {
return null;
this.refreshToken = null;
}
}
return null;
}
isTokenExpired() {
const payload = this.getTokenPayload();
const exp = payload && payload.exp;
if (exp) {
return Math.round(new Date().getTime() / 1000) > exp;
}
return undefined;
}
let payload = null;
isAuthenticated() {
// FAIL: There's no token, so user is not authenticated.
if (!this.accessToken) {
return false;
}
// PASS: There is a token, but in a different format
if (this.accessToken.split('.').length !== 3) {
return true;
if (this.accessToken && this.accessToken.split('.').length === 3) {
try {
const base64 = this.accessToken.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
payload = JSON.parse(decodeURIComponent(escape(window.atob(base64))));
} catch (e) {
payload = null;
}
}
// PASS: Non-JWT token that looks like JWT (isTokenExpired === undefined)
// PASS or FAIL: test isTokenExpired.
return this.isTokenExpired() !== true;
this.payload = payload;
this.exp = payload ? parseInt(payload.exp, 10) : NaN;
this.hasDataStored = true;
return {
accessToken: this.accessToken,
refreshToken: this.refreshToken,
payload: this.payload,
exp: this.exp
};
}
deleteData() {
this.accessToken = null;
this.refreshToken = null;
this.payload = null;
this.exp = null;
/* get and set token from response */
this.hasDataStored = false;
}
getTokenFromResponse(response, tokenProp, tokenName, tokenRoot) {
if (!response) return null;
if (!response) return undefined;
const responseTokenProp = response[tokenProp];
@@ -135,38 +174,11 @@ export class Authentication {
return tokenRootData ? tokenRootData[tokenName] : responseTokenProp[tokenName];
}
return response[tokenName] === undefined ? null : response[tokenName];
}
const token = response[tokenName] === undefined ? null : response[tokenName];
setAccessTokenFromResponse(response) {
const config = this.config;
const newToken = this.getTokenFromResponse(response, config.accessTokenProp, config.accessTokenName, config.accessTokenRoot);
if (!token) throw new Error('Token not found in response');
if (!newToken) throw new Error('Token not found in response');
this.accessToken = newToken;
}
setRefreshTokenFromResponse(response) {
const config = this.config;
const newToken = this.getTokenFromResponse(response, config.refreshTokenProp, config.refreshTokenName, config.refreshTokenRoot);
if (!newToken) throw new Error('Token not found in response');
this.refreshToken = newToken;
}
setTokensFromResponse(response) {
this.setAccessTokenFromResponse(response);
if (this.config.useRefreshToken) {
this.setRefreshTokenFromResponse(response);
}
}
removeTokens() {
this.accessToken = null;
this.refreshToken = null;
return token;
}
@@ -122,10 +122,7 @@ export class BaseConfig {
// Determines the `window` property name upon which aurelia-authentication data is stored (Default: `window.localStorage`)
storage = 'localStorage';
// The property name used when storing the access token locally
accessTokenStorage = 'aurelia_access_token';
// The property name used when storing the refresh token locally
refreshTokenStorage = 'aurelia_refresh_token';
storageKey = 'aurelia_authentication';
//OAuth provider specific related configuration
// ============================================
@@ -296,17 +293,17 @@ export class BaseConfig {
console.warn('BaseConfig.tokenName is deprecated. Use BaseConfig.accessTokenName instead.');
this._tokenName = tokenName;
this.accessTokenName = tokenName;
this.accessTokenStorage = this.tokenPrefix ? this.tokenPrefix + '_' + this.tokenName : this.tokenName;
this.storageKey = this.tokenPrefix ? this.tokenPrefix + '_' + this.tokenName : this.tokenName;
return tokenName;
}
get tokenName() {
return this._tokenName;
}
set tokenPrefix(tokenPrefix) {
console.warn('BaseConfig.tokenPrefix is deprecated. Use BaseConfig.accessTokenStorage instead.');
console.warn('BaseConfig.tokenPrefix is deprecated. Use BaseConfig.storageKey instead.');
this._tokenPrefix = tokenPrefix;
this.accessTokenStorage = this.tokenPrefix ? this.tokenPrefix + '_' + this.tokenName : this.tokenName;
this.storageKey = this.tokenPrefix ? this.tokenPrefix + '_' + this.tokenName : this.tokenName;
return tokenPrefix;
}
get tokenPrefix() {
Oops, something went wrong.

0 comments on commit b98d839

Please sign in to comment.