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:
parent
5e24a70a87
commit
78ae9383fb
@ -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>;
|
||||||
|
@ -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]);
|
||||||
|
@ -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> {
|
||||||
|
Loading…
Reference in New Issue
Block a user