From bf38ca9578e4dc666a2ac3551d123f13c6371236 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 2 Nov 2017 17:49:00 -0400 Subject: [PATCH] convert token service to ts --- src/background.js | 4 +- src/services/token.service.ts | 177 ++++++++++++++++++++++++++++++++++ src/services/utils.service.ts | 20 +++- 3 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 src/services/token.service.ts diff --git a/src/background.js b/src/background.js index bb90c02631..6c68addde5 100644 --- a/src/background.js +++ b/src/background.js @@ -1,11 +1,11 @@ // Service imports import AppIdService from './services/appId.service'; import ConstantsService from './services/constants.service'; +import CryptoService from './services/crypto.service'; import i18nService from './services/i18nService.js'; import LockService from './services/lockService.js'; -import UtilsService from './services/utils.service'; -import CryptoService from './services/crypto.service'; import PasswordGenerationService from './services/passwordGeneration.service'; +import UtilsService from './services/utils.service'; // Model imports import { AttachmentData } from './models/data/attachmentData'; diff --git a/src/services/token.service.ts b/src/services/token.service.ts new file mode 100644 index 0000000000..e90f2fdb6f --- /dev/null +++ b/src/services/token.service.ts @@ -0,0 +1,177 @@ +import ConstantsService from './constants.service'; +import UtilsService from './utils.service'; + +const Keys = { + accessToken: 'accessToken', + refreshToken: 'refreshToken', + twoFactorTokenPrefix: 'twoFactorToken_', +}; + +export default class TokenService { + token: string; + decodedToken: any; + refreshToken: string; + + // TODO: fix callbacks + setTokens(accessToken: string, refreshToken: string): Promise { + return Promise.all([ + this.setToken(accessToken), + this.setRefreshToken(refreshToken), + ]); + } + + // TODO: fix callback implementations + setToken(token: string): Promise { + this.token = token; + this.decodedToken = null; + return UtilsService.saveObjToStorage(Keys.accessToken, token); + } + + // TODO: fix callback implementations + async getToken(): Promise { + if (this.token != null) { + return this.token; + } + + this.token = await UtilsService.getObjFromStorage(Keys.accessToken); + return this.token; + } + + // TODO: fix callback implementations + setRefreshToken(refreshToken: string): Promise { + this.refreshToken = refreshToken; + return UtilsService.saveObjToStorage(Keys.refreshToken, refreshToken); + } + + // TODO: fix callback implementations + async getRefreshToken(): Promise { + if (this.refreshToken != null) { + return this.refreshToken; + } + + this.refreshToken = await UtilsService.getObjFromStorage(Keys.refreshToken); + return this.refreshToken; + } + + // TODO: fix callback implementations + setTwoFactorToken(token: string, email: string): Promise { + return UtilsService.saveObjToStorage(Keys.twoFactorTokenPrefix + email, token); + } + + getTwoFactorToken(email: string): Promise { + return UtilsService.getObjFromStorage(Keys.twoFactorTokenPrefix + email); + } + + // TODO: fix callback implementations + clearTwoFactorToken(email: string): Promise { + return UtilsService.removeFromStorage(Keys.twoFactorTokenPrefix + email); + } + + clearToken(): Promise { + this.token = null; + this.decodedToken = null; + this.refreshToken = null; + + return Promise.all([ + UtilsService.removeFromStorage(Keys.accessToken), + UtilsService.removeFromStorage(Keys.refreshToken), + ]); + } + + // jwthelper methods + // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js + + decodeToken(): any { + if (this.decodedToken) { + return this.decodedToken; + } + + if (this.token == null) { + throw new Error('Token not found.'); + } + + const parts = this.token.split('.'); + if (parts.length !== 3) { + throw new Error('JWT must have 3 parts'); + } + + const decoded = UtilsService.urlBase64Decode(parts[1]); + if (decoded == null) { + throw new Error('Cannot decode the token'); + } + + this.decodedToken = JSON.parse(decoded); + return this.decodedToken; + } + + getTokenExpirationDate(): Date { + const decoded = this.decodeToken(); + if (typeof decoded.exp === 'undefined') { + return null; + } + + const d = new Date(0); // The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + return d; + } + + tokenSecondsRemaining(offsetSeconds: number = 0): number { + const d = this.getTokenExpirationDate(); + if (d == null) { + return 0; + } + + const msRemaining = d.valueOf() - (new Date().valueOf() + (offsetSeconds * 1000)); + return Math.round(msRemaining / 1000); + } + + tokenNeedsRefresh(minutes: number = 5): boolean { + const sRemaining = this.tokenSecondsRemaining(); + return sRemaining < (60 * minutes); + } + + getUserId(): string { + const decoded = this.decodeToken(); + if (typeof decoded.sub === 'undefined') { + throw new Error('No user id found'); + } + + return decoded.sub as string; + } + + getEmail(): string { + const decoded = this.decodeToken(); + if (typeof decoded.email === 'undefined') { + throw new Error('No email found'); + } + + return decoded.email as string; + } + + getName(): string { + const decoded = this.decodeToken(); + if (typeof decoded.name === 'undefined') { + throw new Error('No name found'); + } + + return decoded.name as string; + } + + getPremium(): boolean { + const decoded = this.decodeToken(); + if (typeof decoded.premium === 'undefined') { + return false; + } + + return decoded.premium as boolean; + } + + getIssuer(): string { + const decoded = this.decodeToken(); + if (typeof decoded.iss === 'undefined') { + throw new Error('No issuer found'); + } + + return decoded.iss as string; + } +} diff --git a/src/services/utils.service.ts b/src/services/utils.service.ts index e04dccdc8e..2c02556c53 100644 --- a/src/services/utils.service.ts +++ b/src/services/utils.service.ts @@ -8,7 +8,25 @@ const AnalyticsIds = { }; export default class UtilsService { - // ref: http://stackoverflow.com/a/2117523/1090359 + static urlBase64Decode(str: string): string { + let output = str.replace(/-/g, '+').replace(/_/g, '/'); + switch (output.length % 4) { + case 0: + break; + case 2: + output += '=='; + break; + case 3: + output += '='; + break; + default: + throw new Error('Illegal base64url string!'); + } + + return decodeURIComponent(escape(window.atob(output))); + } + + // ref: http://stackoverflow.com/a/2117523/1090359 static newGuid(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { // tslint:disable-next-line