From 78ae9383fbc1035bd27c022756cfa1f510ae16c9 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 21 Jun 2021 18:48:06 -0400 Subject: [PATCH] Persist API key creds for token refresh. (#414) * Persist API key creds for token refresh. * Linter fixes --- common/src/abstractions/apiKey.service.ts | 4 +- common/src/services/api.service.ts | 79 +++++++++++------------ common/src/services/apiKey.service.ts | 23 ++++++- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/common/src/abstractions/apiKey.service.ts b/common/src/abstractions/apiKey.service.ts index cf62765880..bc4b8a4437 100644 --- a/common/src/abstractions/apiKey.service.ts +++ b/common/src/abstractions/apiKey.service.ts @@ -1,6 +1,8 @@ export abstract class ApiKeyService { - setInformation: (clientId: string) => Promise; + setInformation: (clientId: string, clientSecret: string) => Promise; clear: () => Promise; + getClientId: () => Promise; + getClientSecret: () => Promise; getEntityType: () => Promise; getEntityId: () => Promise; isAuthenticated: () => Promise; diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index b31a1b7354..51c1c1456e 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -1312,8 +1312,8 @@ export class ApiService implements ApiServiceAbstraction { async getActiveBearerToken(): Promise { let accessToken = await this.tokenService.getToken(); if (this.tokenService.tokenNeedsRefresh()) { - const tokenResponse = await this.doRefreshToken(); - accessToken = tokenResponse.accessToken; + await this.doRefreshToken(); + accessToken = await this.tokenService.getToken(); } return accessToken; } @@ -1358,6 +1358,43 @@ export class ApiService implements ApiServiceAbstraction { } } + protected async doRefreshToken(): Promise { + const refreshToken = await this.tokenService.getRefreshToken(); + if (refreshToken == null || refreshToken === '') { + throw new Error(); + } + const headers = new Headers({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'Accept': 'application/json', + 'Device-Type': this.deviceType, + }); + if (this.customUserAgent != null) { + headers.set('User-Agent', this.customUserAgent); + } + + const decodedToken = this.tokenService.decodeToken(); + const response = await this.fetch(new Request(this.identityBaseUrl + '/connect/token', { + body: this.qsStringify({ + grant_type: 'refresh_token', + client_id: decodedToken.client_id, + refresh_token: refreshToken, + }), + cache: 'no-store', + credentials: this.getCredentials(), + headers: headers, + method: 'POST', + })); + + if (response.status === 200) { + const responseJson = await response.json(); + const tokenResponse = new IdentityTokenResponse(responseJson); + await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken); + } else { + const error = await this.handleError(response, true, true); + return Promise.reject(error); + } + } + private async send(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body: any, authed: boolean, hasResponse: boolean, apiUrl?: string, alterHeaders?: (headers: Headers) => void): Promise { @@ -1427,44 +1464,6 @@ export class ApiService implements ApiServiceAbstraction { return new ErrorResponse(responseJson, response.status, tokenError); } - private async doRefreshToken(): Promise { - const refreshToken = await this.tokenService.getRefreshToken(); - if (refreshToken == null || refreshToken === '') { - throw new Error(); - } - const headers = new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', - 'Accept': 'application/json', - 'Device-Type': this.deviceType, - }); - if (this.customUserAgent != null) { - headers.set('User-Agent', this.customUserAgent); - } - - const decodedToken = this.tokenService.decodeToken(); - const response = await this.fetch(new Request(this.identityBaseUrl + '/connect/token', { - body: this.qsStringify({ - grant_type: 'refresh_token', - client_id: decodedToken.client_id, - refresh_token: refreshToken, - }), - cache: 'no-store', - credentials: this.getCredentials(), - headers: headers, - method: 'POST', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - const tokenResponse = new IdentityTokenResponse(responseJson); - await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken); - return tokenResponse; - } else { - const error = await this.handleError(response, true, true); - return Promise.reject(error); - } - } - private qsStringify(params: any): string { return Object.keys(params).map(key => { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); diff --git a/common/src/services/apiKey.service.ts b/common/src/services/apiKey.service.ts index 1e09ac7e41..0b67781b20 100644 --- a/common/src/services/apiKey.service.ts +++ b/common/src/services/apiKey.service.ts @@ -6,6 +6,7 @@ import { Utils } from '../misc/utils'; const Keys = { clientId: 'clientId', + clientSecret: 'clientSecret', entityType: 'entityType', entityId: 'entityId', }; @@ -13,13 +14,15 @@ const Keys = { export class ApiKeyService implements ApiKeyServiceAbstraction { private clientId: string; + private clientSecret: string; private entityType: string; private entityId: string; constructor(private tokenService: TokenService, private storageService: StorageService) { } - async setInformation(clientId: string) { + async setInformation(clientId: string, clientSecret: string) { this.clientId = clientId; + this.clientSecret = clientSecret; const idParts = clientId.split('.'); if (idParts.length !== 2 || !Utils.isGuid(idParts[1])) { @@ -31,6 +34,21 @@ export class ApiKeyService implements ApiKeyServiceAbstraction { await this.storageService.save(Keys.clientId, this.clientId); await this.storageService.save(Keys.entityId, this.entityId); await this.storageService.save(Keys.entityType, this.entityType); + await this.storageService.save(Keys.clientSecret, this.clientSecret); + } + + async getClientId(): Promise { + if (this.clientId == null) { + this.clientId = await this.storageService.get(Keys.clientId); + } + return this.clientId; + } + + async getClientSecret(): Promise { + if (this.clientSecret == null) { + this.clientSecret = await this.storageService.get(Keys.clientSecret); + } + return this.clientSecret; } async getEntityType(): Promise { @@ -49,10 +67,11 @@ export class ApiKeyService implements ApiKeyServiceAbstraction { async clear(): Promise { await this.storageService.remove(Keys.clientId); + await this.storageService.remove(Keys.clientSecret); await this.storageService.remove(Keys.entityId); await this.storageService.remove(Keys.entityType); - this.clientId = this.entityId = this.entityType = null; + this.clientId = this.clientSecret = this.entityId = this.entityType = null; } async isAuthenticated(): Promise {