From 647b087fa7c8afdf46a387f0d85532fefbaa86c5 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 22 Jun 2021 16:13:08 -0400 Subject: [PATCH] Refresh token with api key (#135) * Do not persist client creds on logout * Override refreshing token flow with re-authentication flow * Update jslib * PR review comments --- jslib | 2 +- src/app/services/services.module.ts | 8 ++++-- src/bwdc.ts | 9 +++++-- src/main.ts | 2 +- src/services/api.service.ts | 30 +++++++++++++++++++++ src/services/auth.service.ts | 7 ++++- src/services/keytarSecureStorage.service.ts | 4 +++ src/services/nodeApi.service.ts | 16 +++++++++++ 8 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 src/services/api.service.ts create mode 100644 src/services/nodeApi.service.ts diff --git a/jslib b/jslib index d7682cde..78ae9383 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit d7682cde3bac4f4a8403b5573bf5feaa4c47172c +Subproject commit 78ae9383fbc1035bd27c022756cfa1f510ae16c9 diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 9a3f6ceb..c71602f7 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -21,7 +21,6 @@ import { SyncService } from '../../services/sync.service'; import { BroadcasterService } from 'jslib-angular/services/broadcaster.service'; import { ValidationService } from 'jslib-angular/services/validation.service'; -import { ApiService } from 'jslib-common/services/api.service'; import { ApiKeyService } from 'jslib-common/services/apiKey.service'; import { AppIdService } from 'jslib-common/services/appId.service'; import { ConstantsService } from 'jslib-common/services/constants.service'; @@ -55,6 +54,7 @@ import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstra import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service'; +import { ApiService, refreshToken } from '../../services/api.service'; import { AuthService } from '../../services/auth.service'; const logService = new ElectronLogService(); @@ -70,7 +70,7 @@ const cryptoService = new CryptoService(storageService, secureStorageService, cr platformUtilsService, logService); const appIdService = new AppIdService(storageService); const tokenService = new TokenService(storageService); -const apiService = new ApiService(tokenService, platformUtilsService, +const apiService = new ApiService(tokenService, platformUtilsService, refreshTokenCallback, async (expired: boolean) => messagingService.send('logout', { expired: expired })); const environmentService = new EnvironmentService(apiService, storageService, null); const userService = new UserService(tokenService, storageService); @@ -86,6 +86,10 @@ const policyService = new PolicyService(userService, storageService); containerService.attachToWindow(window); +function refreshTokenCallback(): Promise { + return refreshToken(apiKeyService, authService); +} + export function initFactory(): Function { return async () => { await environmentService.setUrlsFromStorage(); diff --git a/src/bwdc.ts b/src/bwdc.ts index 8dfad279..730e095b 100644 --- a/src/bwdc.ts +++ b/src/bwdc.ts @@ -9,6 +9,7 @@ import { ConfigurationService } from './services/configuration.service'; import { I18nService } from './services/i18n.service'; import { KeytarSecureStorageService } from './services/keytarSecureStorage.service'; import { LowdbStorageService } from './services/lowdbStorage.service'; +import { NodeApiService } from './services/nodeApi.service'; import { SyncService } from './services/sync.service'; import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service'; @@ -25,11 +26,11 @@ import { NoopMessagingService } from 'jslib-common/services/noopMessaging.servic import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; import { TokenService } from 'jslib-common/services/token.service'; import { UserService } from 'jslib-common/services/user.service'; -import { NodeApiService } from 'jslib-node/services/nodeApi.service'; import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { Program } from './program'; +import { refreshToken } from './services/api.service'; // tslint:disable-next-line const packageJson = require('./package.json'); @@ -90,7 +91,7 @@ export class Main { this.appIdService = new AppIdService(this.storageService); this.tokenService = new TokenService(this.storageService); this.messagingService = new NoopMessagingService(); - this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, + this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.refreshTokenCallback, async (expired: boolean) => await this.logout()); this.environmentService = new EnvironmentService(this.apiService, this.storageService, null); this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService); @@ -117,6 +118,10 @@ export class Main { await this.apiKeyService.clear(); } + refreshTokenCallback() { + return refreshToken(this.apiKeyService, this.authService); + } + private async init() { await this.storageService.init(); this.containerService.attachToWindow(global); diff --git a/src/main.ts b/src/main.ts index bd10bd0f..6be0a434 100644 --- a/src/main.ts +++ b/src/main.ts @@ -67,7 +67,7 @@ export class Main { this.messagingMain.onMessage(message); }); - this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector'); + this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null); } bootstrap() { diff --git a/src/services/api.service.ts b/src/services/api.service.ts new file mode 100644 index 00000000..ef34dd0b --- /dev/null +++ b/src/services/api.service.ts @@ -0,0 +1,30 @@ +import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; +import { AuthService } from 'jslib-common/abstractions/auth.service'; +import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { TokenService } from 'jslib-common/abstractions/token.service'; + +import { ApiService as ApiServiceBase } from 'jslib-common/services/api.service'; + +export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) { + try { + const clientId = await apiKeyService.getClientId(); + const clientSecret = await apiKeyService.getClientSecret(); + if (clientId != null && clientSecret != null) { + await authService.logInApiKey(clientId, clientSecret); + } + } catch (e) { + return Promise.reject(e); + } +} + +export class ApiService extends ApiServiceBase { + constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, + private refreshTokenCallback: () => Promise, logoutCallback: (expired: boolean) => Promise, + customUserAgent: string = null) { + super(tokenService, platformUtilsService, logoutCallback, customUserAgent); + } + + doRefreshToken(): Promise { + return this.refreshTokenCallback(); + } +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 1fb0c347..638d5658 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -36,6 +36,11 @@ export class AuthService extends AuthServiceBase { return await super.logInApiKey(clientId, clientSecret); } + async logOut(callback: Function) { + this.apiKeyService.clear(); + super.logOut(callback); + } + private async organizationLogInHelper(clientId: string, clientSecret: string) { const appId = await this.appIdService.getAppId(); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); @@ -49,7 +54,7 @@ export class AuthService extends AuthServiceBase { const tokenResponse = response as IdentityTokenResponse; result.resetMasterPassword = tokenResponse.resetMasterPassword; await this.tokenService.setToken(tokenResponse.accessToken); - await this.apiKeyService.setInformation(clientId); + await this.apiKeyService.setInformation(clientId, clientSecret); return result; } diff --git a/src/services/keytarSecureStorage.service.ts b/src/services/keytarSecureStorage.service.ts index be95a981..3149fdc6 100644 --- a/src/services/keytarSecureStorage.service.ts +++ b/src/services/keytarSecureStorage.service.ts @@ -15,6 +15,10 @@ export class KeytarSecureStorageService implements StorageService { }); } + async has(key: string): Promise { + return (await this.get(key)) != null; + } + save(key: string, obj: any): Promise { return setPassword(this.serviceName, key, JSON.stringify(obj)); } diff --git a/src/services/nodeApi.service.ts b/src/services/nodeApi.service.ts new file mode 100644 index 00000000..2e3f9bfe --- /dev/null +++ b/src/services/nodeApi.service.ts @@ -0,0 +1,16 @@ +import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { TokenService } from 'jslib-common/abstractions/token.service'; + +import { NodeApiService as NodeApiServiceBase } from 'jslib-node/services/nodeApi.service'; + +export class NodeApiService extends NodeApiServiceBase { + constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, + private refreshTokenCallback: () => Promise, logoutCallback: (expired: boolean) => Promise, + customUserAgent: string = null) { + super(tokenService, platformUtilsService, logoutCallback, customUserAgent); + } + + doRefreshToken(): Promise { + return this.refreshTokenCallback(); + } +}