1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-02 23:11:40 +01:00

Persist API key creds for token refresh. (#414)

* Persist API key creds for token refresh.

* Linter fixes
This commit is contained in:
Matt Gibson 2021-06-21 18:48:06 -04:00 committed by GitHub
parent 5e24a70a87
commit 78ae9383fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 43 deletions

View File

@ -1,6 +1,8 @@
export abstract class ApiKeyService { export abstract class ApiKeyService {
setInformation: (clientId: string) => Promise<any>; setInformation: (clientId: string, clientSecret: string) => Promise<any>;
clear: () => Promise<any>; clear: () => Promise<any>;
getClientId: () => Promise<string>;
getClientSecret: () => Promise<string>;
getEntityType: () => Promise<string>; getEntityType: () => Promise<string>;
getEntityId: () => Promise<string>; getEntityId: () => Promise<string>;
isAuthenticated: () => Promise<boolean>; isAuthenticated: () => Promise<boolean>;

View File

@ -1312,8 +1312,8 @@ export class ApiService implements ApiServiceAbstraction {
async getActiveBearerToken(): Promise<string> { async getActiveBearerToken(): Promise<string> {
let accessToken = await this.tokenService.getToken(); let accessToken = await this.tokenService.getToken();
if (this.tokenService.tokenNeedsRefresh()) { if (this.tokenService.tokenNeedsRefresh()) {
const tokenResponse = await this.doRefreshToken(); await this.doRefreshToken();
accessToken = tokenResponse.accessToken; accessToken = await this.tokenService.getToken();
} }
return accessToken; return accessToken;
} }
@ -1358,6 +1358,43 @@ export class ApiService implements ApiServiceAbstraction {
} }
} }
protected async doRefreshToken(): Promise<void> {
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, private async send(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body: any,
authed: boolean, hasResponse: boolean, apiUrl?: string, authed: boolean, hasResponse: boolean, apiUrl?: string,
alterHeaders?: (headers: Headers) => void): Promise<any> { alterHeaders?: (headers: Headers) => void): Promise<any> {
@ -1427,44 +1464,6 @@ export class ApiService implements ApiServiceAbstraction {
return new ErrorResponse(responseJson, response.status, tokenError); return new ErrorResponse(responseJson, response.status, tokenError);
} }
private async doRefreshToken(): Promise<IdentityTokenResponse> {
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 { private qsStringify(params: any): string {
return Object.keys(params).map(key => { return Object.keys(params).map(key => {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);

View File

@ -6,6 +6,7 @@ import { Utils } from '../misc/utils';
const Keys = { const Keys = {
clientId: 'clientId', clientId: 'clientId',
clientSecret: 'clientSecret',
entityType: 'entityType', entityType: 'entityType',
entityId: 'entityId', entityId: 'entityId',
}; };
@ -13,13 +14,15 @@ const Keys = {
export class ApiKeyService implements ApiKeyServiceAbstraction { export class ApiKeyService implements ApiKeyServiceAbstraction {
private clientId: string; private clientId: string;
private clientSecret: string;
private entityType: string; private entityType: string;
private entityId: string; private entityId: string;
constructor(private tokenService: TokenService, private storageService: StorageService) { } constructor(private tokenService: TokenService, private storageService: StorageService) { }
async setInformation(clientId: string) { async setInformation(clientId: string, clientSecret: string) {
this.clientId = clientId; this.clientId = clientId;
this.clientSecret = clientSecret;
const idParts = clientId.split('.'); const idParts = clientId.split('.');
if (idParts.length !== 2 || !Utils.isGuid(idParts[1])) { 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.clientId, this.clientId);
await this.storageService.save(Keys.entityId, this.entityId); await this.storageService.save(Keys.entityId, this.entityId);
await this.storageService.save(Keys.entityType, this.entityType); await this.storageService.save(Keys.entityType, this.entityType);
await this.storageService.save(Keys.clientSecret, this.clientSecret);
}
async getClientId(): Promise<string> {
if (this.clientId == null) {
this.clientId = await this.storageService.get<string>(Keys.clientId);
}
return this.clientId;
}
async getClientSecret(): Promise<string> {
if (this.clientSecret == null) {
this.clientSecret = await this.storageService.get<string>(Keys.clientSecret);
}
return this.clientSecret;
} }
async getEntityType(): Promise<string> { async getEntityType(): Promise<string> {
@ -49,10 +67,11 @@ export class ApiKeyService implements ApiKeyServiceAbstraction {
async clear(): Promise<any> { async clear(): Promise<any> {
await this.storageService.remove(Keys.clientId); await this.storageService.remove(Keys.clientId);
await this.storageService.remove(Keys.clientSecret);
await this.storageService.remove(Keys.entityId); await this.storageService.remove(Keys.entityId);
await this.storageService.remove(Keys.entityType); 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<boolean> { async isAuthenticated(): Promise<boolean> {