Skip to content
This repository has been archived by the owner on Jun 17, 2022. It is now read-only.

Commit

Permalink
Generalize token refreshing to include reauth by api key (#456)
Browse files Browse the repository at this point in the history
(cherry picked from commit 1f01279)
  • Loading branch information
MGibson1 committed Aug 13, 2021
1 parent 880fd10 commit 0a2ff12
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 22 deletions.
6 changes: 5 additions & 1 deletion common/src/abstractions/token.service.ts
Expand Up @@ -2,11 +2,15 @@ export abstract class TokenService {
token: string;
decodedToken: any;
refreshToken: string;
setTokens: (accessToken: string, refreshToken: string) => Promise<any>;
setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise<any>;
setToken: (token: string) => Promise<any>;
getToken: () => Promise<string>;
setRefreshToken: (refreshToken: string) => Promise<any>;
getRefreshToken: () => Promise<string>;
setClientId: (clientId: string) => Promise<any>;
getClientId: () => Promise<string>;
setClientSecret: (clientSecret: string) => Promise<any>;
getClientSecret: () => Promise<string>;
toggleTokens: () => Promise<any>;
setTwoFactorToken: (token: string, email: string) => Promise<any>;
getTwoFactorToken: (email: string) => Promise<string>;
Expand Down
32 changes: 29 additions & 3 deletions common/src/services/api.service.ts
Expand Up @@ -165,6 +165,7 @@ import { IdentityCaptchaResponse } from '../models/response/identityCaptchaRespo
import { SendAccessView } from '../models/view/sendAccessView';

export class ApiService implements ApiServiceAbstraction {
protected apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>;
private device: DeviceType;
private deviceType: string;
private isWebClient = false;
Expand Down Expand Up @@ -226,7 +227,7 @@ export class ApiService implements ApiServiceAbstraction {

async refreshIdentityToken(): Promise<any> {
try {
await this.doRefreshToken();
await this.doAuthRefresh();
} catch (e) {
return Promise.reject(null);
}
Expand Down Expand Up @@ -1415,7 +1416,7 @@ export class ApiService implements ApiServiceAbstraction {
async getActiveBearerToken(): Promise<string> {
let accessToken = await this.tokenService.getToken();
if (this.tokenService.tokenNeedsRefresh()) {
await this.doRefreshToken();
await this.doAuthRefresh();
accessToken = await this.tokenService.getToken();
}
return accessToken;
Expand Down Expand Up @@ -1461,6 +1462,31 @@ export class ApiService implements ApiServiceAbstraction {
}
}

protected async doAuthRefresh(): Promise<void> {
const refreshToken = await this.tokenService.getRefreshToken();
if (refreshToken != null && refreshToken !== '') {
return this.doRefreshToken();
}

const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
if (!Utils.isNullOrWhitespace(clientId) && !Utils.isNullOrWhitespace(clientSecret)) {
return this.doApiTokenRefresh();
}

throw new Error('Cannot refresh token, no refresh token or api keys are stored');
}

protected async doApiTokenRefresh(): Promise<void> {
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
if (Utils.isNullOrWhitespace(clientId) || Utils.isNullOrWhitespace(clientSecret) || this.apiKeyRefresh == null) {
throw new Error();
}

await this.apiKeyRefresh(clientId, clientSecret);
}

protected async doRefreshToken(): Promise<void> {
const refreshToken = await this.tokenService.getRefreshToken();
if (refreshToken == null || refreshToken === '') {
Expand Down Expand Up @@ -1491,7 +1517,7 @@ export class ApiService implements ApiServiceAbstraction {
if (response.status === 200) {
const responseJson = await response.json();
const tokenResponse = new IdentityTokenResponse(responseJson);
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, null);
} else {
const error = await this.handleError(response, true, true);
return Promise.reject(error);
Expand Down
4 changes: 2 additions & 2 deletions common/src/services/auth.service.ts
Expand Up @@ -280,7 +280,7 @@ export class AuthService implements AuthServiceAbstraction {

let emailPassword: string[] = [];
let codeCodeVerifier: string[] = [];
let clientIdClientSecret: string[] = [];
let clientIdClientSecret: [string, string] = [null, null];

if (email != null && hashedPassword != null) {
emailPassword = [email, hashedPassword];
Expand Down Expand Up @@ -344,7 +344,7 @@ export class AuthService implements AuthServiceAbstraction {
await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email);
}

await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, clientIdClientSecret);
await this.userService.setInformation(this.tokenService.getUserId(), this.tokenService.getEmail(),
tokenResponse.kdf, tokenResponse.kdfIterations);
if (this.setCryptoKeys) {
Expand Down
73 changes: 58 additions & 15 deletions common/src/services/token.service.ts
Expand Up @@ -9,31 +9,61 @@ const Keys = {
accessToken: 'accessToken',
refreshToken: 'refreshToken',
twoFactorTokenPrefix: 'twoFactorToken_',
clientId: 'apikey_clientId',
clientSecret: 'apikey_clientSecret',
};

export class TokenService implements TokenServiceAbstraction {
token: string;
decodedToken: any;
refreshToken: string;
clientId: string;
clientSecret: string;

constructor(private storageService: StorageService) {
}

async setTokens(accessToken: string, refreshToken: string): Promise<any> {
async setTokens(accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]): Promise<any> {
await this.setToken(accessToken);
await this.setRefreshToken(refreshToken);
if (clientIdClientSecret != null) {
await this.setClientId(clientIdClientSecret[0]);
await this.setClientSecret(clientIdClientSecret[1]);
}
}

async setToken(token: string): Promise<any> {
this.token = token;
this.decodedToken = null;
async setClientId(clientId: string): Promise<any> {
this.clientId = clientId;
return this.storeTokenValue(Keys.clientId, clientId);
}

if (await this.skipTokenStorage()) {
// if we have a vault timeout and the action is log out, don't store token
return;
async getClientId(): Promise<string> {
if (this.clientId != null) {
return this.clientId;
}

return this.storageService.save(Keys.accessToken, token);
this.clientId = await this.storageService.get<string>(Keys.clientId);
return this.clientId;
}

async setClientSecret(clientSecret: string): Promise<any> {
this.clientSecret = clientSecret;
return this.storeTokenValue(Keys.clientSecret, clientSecret);
}

async getClientSecret(): Promise<string> {
if (this.clientSecret != null) {
return this.clientSecret;
}

this.clientSecret = await this.storageService.get<string>(Keys.clientSecret);
return this.clientSecret;
}

async setToken(token: string): Promise<any> {
this.token = token;
this.decodedToken = null;
return this.storeTokenValue(Keys.accessToken, token);
}

async getToken(): Promise<string> {
Expand All @@ -47,13 +77,7 @@ export class TokenService implements TokenServiceAbstraction {

async setRefreshToken(refreshToken: string): Promise<any> {
this.refreshToken = refreshToken;

if (await this.skipTokenStorage()) {
// if we have a vault timeout and the action is log out, don't store token
return;
}

return this.storageService.save(Keys.refreshToken, refreshToken);
return this.storeTokenValue(Keys.refreshToken, refreshToken);
}

async getRefreshToken(): Promise<string> {
Expand All @@ -68,18 +92,24 @@ export class TokenService implements TokenServiceAbstraction {
async toggleTokens(): Promise<any> {
const token = await this.getToken();
const refreshToken = await this.getRefreshToken();
const clientId = await this.getClientId();
const clientSecret = await this.getClientSecret();
const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey);
const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey);
if ((timeout != null || timeout === 0) && action === 'logOut') {
// if we have a vault timeout and the action is log out, reset tokens
await this.clearToken();
this.token = token;
this.refreshToken = refreshToken;
this.clientId = clientId;
this.clientSecret = clientSecret;
return;
}

await this.setToken(token);
await this.setRefreshToken(refreshToken);
await this.setClientId(clientId);
await this.setClientSecret(clientSecret);
}

setTwoFactorToken(token: string, email: string): Promise<any> {
Expand All @@ -98,9 +128,13 @@ export class TokenService implements TokenServiceAbstraction {
this.token = null;
this.decodedToken = null;
this.refreshToken = null;
this.clientId = null;
this.clientSecret = null;

await this.storageService.remove(Keys.accessToken);
await this.storageService.remove(Keys.refreshToken);
await this.storageService.remove(Keys.clientId);
await this.storageService.remove(Keys.clientSecret);
}

// jwthelper methods
Expand Down Expand Up @@ -209,6 +243,15 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.iss as string;
}

private async storeTokenValue(key: string, value: string) {
if (await this.skipTokenStorage()) {
// if we have a vault timeout and the action is log out, don't store token
return;
}

return this.storageService.save(key, value);
}

private async skipTokenStorage(): Promise<boolean> {
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
Expand Down
3 changes: 2 additions & 1 deletion node/src/services/nodeApi.service.ts
Expand Up @@ -17,8 +17,9 @@ import { TokenService } from 'jslib-common/abstractions/token.service';
export class NodeApiService extends ApiService {
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null) {
customUserAgent: string = null, apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>) {
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
this.apiKeyRefresh = apiKeyRefresh;
}

nativeFetch(request: Request): Promise<Response> {
Expand Down

0 comments on commit 0a2ff12

Please sign in to comment.