mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01:00
Generalize token refreshing to include reauth by api key (#456)
This commit is contained in:
parent
0180d0cce5
commit
1f0127966e
@ -2,11 +2,15 @@ export abstract class TokenService {
|
|||||||
token: string;
|
token: string;
|
||||||
decodedToken: any;
|
decodedToken: any;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
setTokens: (accessToken: string, refreshToken: string) => Promise<any>;
|
setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise<any>;
|
||||||
setToken: (token: string) => Promise<any>;
|
setToken: (token: string) => Promise<any>;
|
||||||
getToken: () => Promise<string>;
|
getToken: () => Promise<string>;
|
||||||
setRefreshToken: (refreshToken: string) => Promise<any>;
|
setRefreshToken: (refreshToken: string) => Promise<any>;
|
||||||
getRefreshToken: () => Promise<string>;
|
getRefreshToken: () => Promise<string>;
|
||||||
|
setClientId: (clientId: string) => Promise<any>;
|
||||||
|
getClientId: () => Promise<string>;
|
||||||
|
setClientSecret: (clientSecret: string) => Promise<any>;
|
||||||
|
getClientSecret: () => Promise<string>;
|
||||||
toggleTokens: () => Promise<any>;
|
toggleTokens: () => Promise<any>;
|
||||||
setTwoFactorToken: (token: string, email: string) => Promise<any>;
|
setTwoFactorToken: (token: string, email: string) => Promise<any>;
|
||||||
getTwoFactorToken: (email: string) => Promise<string>;
|
getTwoFactorToken: (email: string) => Promise<string>;
|
||||||
|
@ -165,6 +165,7 @@ import { IdentityCaptchaResponse } from '../models/response/identityCaptchaRespo
|
|||||||
import { SendAccessView } from '../models/view/sendAccessView';
|
import { SendAccessView } from '../models/view/sendAccessView';
|
||||||
|
|
||||||
export class ApiService implements ApiServiceAbstraction {
|
export class ApiService implements ApiServiceAbstraction {
|
||||||
|
protected apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>;
|
||||||
private device: DeviceType;
|
private device: DeviceType;
|
||||||
private deviceType: string;
|
private deviceType: string;
|
||||||
private isWebClient = false;
|
private isWebClient = false;
|
||||||
@ -226,7 +227,7 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
|
|
||||||
async refreshIdentityToken(): Promise<any> {
|
async refreshIdentityToken(): Promise<any> {
|
||||||
try {
|
try {
|
||||||
await this.doRefreshToken();
|
await this.doAuthRefresh();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
}
|
}
|
||||||
@ -1415,7 +1416,7 @@ 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()) {
|
||||||
await this.doRefreshToken();
|
await this.doAuthRefresh();
|
||||||
accessToken = await this.tokenService.getToken();
|
accessToken = await this.tokenService.getToken();
|
||||||
}
|
}
|
||||||
return accessToken;
|
return accessToken;
|
||||||
@ -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> {
|
protected async doRefreshToken(): Promise<void> {
|
||||||
const refreshToken = await this.tokenService.getRefreshToken();
|
const refreshToken = await this.tokenService.getRefreshToken();
|
||||||
if (refreshToken == null || refreshToken === '') {
|
if (refreshToken == null || refreshToken === '') {
|
||||||
@ -1491,7 +1517,7 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const responseJson = await response.json();
|
const responseJson = await response.json();
|
||||||
const tokenResponse = new IdentityTokenResponse(responseJson);
|
const tokenResponse = new IdentityTokenResponse(responseJson);
|
||||||
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
|
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, null);
|
||||||
} else {
|
} else {
|
||||||
const error = await this.handleError(response, true, true);
|
const error = await this.handleError(response, true, true);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -280,7 +280,7 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
|
|
||||||
let emailPassword: string[] = [];
|
let emailPassword: string[] = [];
|
||||||
let codeCodeVerifier: string[] = [];
|
let codeCodeVerifier: string[] = [];
|
||||||
let clientIdClientSecret: string[] = [];
|
let clientIdClientSecret: [string, string] = [null, null];
|
||||||
|
|
||||||
if (email != null && hashedPassword != null) {
|
if (email != null && hashedPassword != null) {
|
||||||
emailPassword = [email, hashedPassword];
|
emailPassword = [email, hashedPassword];
|
||||||
@ -344,7 +344,7 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email);
|
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(),
|
await this.userService.setInformation(this.tokenService.getUserId(), this.tokenService.getEmail(),
|
||||||
tokenResponse.kdf, tokenResponse.kdfIterations);
|
tokenResponse.kdf, tokenResponse.kdfIterations);
|
||||||
if (this.setCryptoKeys) {
|
if (this.setCryptoKeys) {
|
||||||
|
@ -9,31 +9,61 @@ const Keys = {
|
|||||||
accessToken: 'accessToken',
|
accessToken: 'accessToken',
|
||||||
refreshToken: 'refreshToken',
|
refreshToken: 'refreshToken',
|
||||||
twoFactorTokenPrefix: 'twoFactorToken_',
|
twoFactorTokenPrefix: 'twoFactorToken_',
|
||||||
|
clientId: 'apikey_clientId',
|
||||||
|
clientSecret: 'apikey_clientSecret',
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TokenService implements TokenServiceAbstraction {
|
export class TokenService implements TokenServiceAbstraction {
|
||||||
token: string;
|
token: string;
|
||||||
decodedToken: any;
|
decodedToken: any;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
|
||||||
constructor(private storageService: StorageService) {
|
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.setToken(accessToken);
|
||||||
await this.setRefreshToken(refreshToken);
|
await this.setRefreshToken(refreshToken);
|
||||||
|
if (clientIdClientSecret != null) {
|
||||||
|
await this.setClientId(clientIdClientSecret[0]);
|
||||||
|
await this.setClientSecret(clientIdClientSecret[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setClientId(clientId: string): Promise<any> {
|
||||||
|
this.clientId = clientId;
|
||||||
|
return this.storeTokenValue(Keys.clientId, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClientId(): Promise<string> {
|
||||||
|
if (this.clientId != null) {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
async setToken(token: string): Promise<any> {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.decodedToken = null;
|
this.decodedToken = null;
|
||||||
|
return this.storeTokenValue(Keys.accessToken, token);
|
||||||
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.accessToken, token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getToken(): Promise<string> {
|
async getToken(): Promise<string> {
|
||||||
@ -47,13 +77,7 @@ export class TokenService implements TokenServiceAbstraction {
|
|||||||
|
|
||||||
async setRefreshToken(refreshToken: string): Promise<any> {
|
async setRefreshToken(refreshToken: string): Promise<any> {
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
|
return this.storeTokenValue(Keys.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRefreshToken(): Promise<string> {
|
async getRefreshToken(): Promise<string> {
|
||||||
@ -68,6 +92,8 @@ export class TokenService implements TokenServiceAbstraction {
|
|||||||
async toggleTokens(): Promise<any> {
|
async toggleTokens(): Promise<any> {
|
||||||
const token = await this.getToken();
|
const token = await this.getToken();
|
||||||
const refreshToken = await this.getRefreshToken();
|
const refreshToken = await this.getRefreshToken();
|
||||||
|
const clientId = await this.getClientId();
|
||||||
|
const clientSecret = await this.getClientSecret();
|
||||||
const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey);
|
const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey);
|
||||||
const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey);
|
const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey);
|
||||||
if ((timeout != null || timeout === 0) && action === 'logOut') {
|
if ((timeout != null || timeout === 0) && action === 'logOut') {
|
||||||
@ -75,11 +101,15 @@ export class TokenService implements TokenServiceAbstraction {
|
|||||||
await this.clearToken();
|
await this.clearToken();
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.setToken(token);
|
await this.setToken(token);
|
||||||
await this.setRefreshToken(refreshToken);
|
await this.setRefreshToken(refreshToken);
|
||||||
|
await this.setClientId(clientId);
|
||||||
|
await this.setClientSecret(clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTwoFactorToken(token: string, email: string): Promise<any> {
|
setTwoFactorToken(token: string, email: string): Promise<any> {
|
||||||
@ -98,9 +128,13 @@ export class TokenService implements TokenServiceAbstraction {
|
|||||||
this.token = null;
|
this.token = null;
|
||||||
this.decodedToken = null;
|
this.decodedToken = null;
|
||||||
this.refreshToken = null;
|
this.refreshToken = null;
|
||||||
|
this.clientId = null;
|
||||||
|
this.clientSecret = null;
|
||||||
|
|
||||||
await this.storageService.remove(Keys.accessToken);
|
await this.storageService.remove(Keys.accessToken);
|
||||||
await this.storageService.remove(Keys.refreshToken);
|
await this.storageService.remove(Keys.refreshToken);
|
||||||
|
await this.storageService.remove(Keys.clientId);
|
||||||
|
await this.storageService.remove(Keys.clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
// jwthelper methods
|
// jwthelper methods
|
||||||
@ -209,6 +243,15 @@ export class TokenService implements TokenServiceAbstraction {
|
|||||||
return decoded.iss as string;
|
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> {
|
private async skipTokenStorage(): Promise<boolean> {
|
||||||
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||||
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||||
|
@ -17,8 +17,9 @@ import { TokenService } from 'jslib-common/abstractions/token.service';
|
|||||||
export class NodeApiService extends ApiService {
|
export class NodeApiService extends ApiService {
|
||||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
|
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
|
||||||
environmentService: EnvironmentService, logoutCallback: (expired: boolean) => Promise<void>,
|
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);
|
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||||
|
this.apiKeyRefresh = apiKeyRefresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeFetch(request: Request): Promise<Response> {
|
nativeFetch(request: Request): Promise<Response> {
|
||||||
|
Loading…
Reference in New Issue
Block a user