diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a1db902bebc..8340c89ab55 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,6 +24,7 @@ apps/web/src/connectors @bitwarden/team-auth-dev bitwarden_license/bit-web/src/app/auth @bitwarden/team-auth-dev libs/angular/src/auth @bitwarden/team-auth-dev libs/common/src/auth @bitwarden/team-auth-dev +libs/token-provider @bitwarden/team-auth-dev ## Tools team files ## apps/browser/src/tools @bitwarden/team-tools-dev diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 6a145fb3210..a36b840a11f 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -328,6 +328,7 @@ import { UserAsymmetricKeysRegenerationApiService, UserAsymmetricKeysRegenerationService, } from "@bitwarden/key-management"; +import { TokenApiService, DefaultTokenApiService } from "@bitwarden/token-provider"; import { SafeInjectionToken } from "@bitwarden/ui-common"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports @@ -742,6 +743,19 @@ const safeProviders: SafeProvider[] = [ provide: HTTP_OPERATIONS, useValue: { createRequest: (url, request) => new Request(url, request) }, }), + safeProvider({ + provide: TokenApiService, + useClass: DefaultTokenApiService, + deps: [ + PlatformUtilsServiceAbstraction, + EnvironmentService, + AppIdServiceAbstraction, + REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, + LogService, + LOGOUT_CALLBACK, + VaultTimeoutSettingsService, + ], + }), safeProvider({ provide: ApiServiceAbstraction, useClass: ApiService, diff --git a/libs/common/src/auth/models/request/identity-token/device.request.ts b/libs/common/src/auth/models/request/identity-token/device.request.ts index 2ad2fbd5ebd..3f12fc07481 100644 --- a/libs/common/src/auth/models/request/identity-token/device.request.ts +++ b/libs/common/src/auth/models/request/identity-token/device.request.ts @@ -1,24 +1 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Jsonify } from "type-fest"; - -import { DeviceType } from "../../../../enums"; -import { PlatformUtilsService } from "../../../../platform/abstractions/platform-utils.service"; - -export class DeviceRequest { - type: DeviceType; - name: string; - identifier: string; - pushToken?: string; - - constructor(appId: string, platformUtilsService: PlatformUtilsService) { - this.type = platformUtilsService.getDevice(); - this.name = platformUtilsService.getDeviceString(); - this.identifier = appId; - this.pushToken = null; - } - - static fromJSON(json: Jsonify) { - return Object.assign(Object.create(DeviceRequest.prototype), json); - } -} +export { DeviceRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/password-token.request.ts b/libs/common/src/auth/models/request/identity-token/password-token.request.ts index 4f2313473da..49059c5c46d 100644 --- a/libs/common/src/auth/models/request/identity-token/password-token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/password-token.request.ts @@ -1,45 +1 @@ -import { ClientType } from "../../../../enums"; -import { Utils } from "../../../../platform/misc/utils"; - -import { DeviceRequest } from "./device.request"; -import { TokenTwoFactorRequest } from "./token-two-factor.request"; -import { TokenRequest } from "./token.request"; - -export class PasswordTokenRequest extends TokenRequest { - constructor( - public email: string, - public masterPasswordHash: string, - protected twoFactor: TokenTwoFactorRequest, - device?: DeviceRequest, - public newDeviceOtp?: string, - ) { - super(twoFactor, device); - } - - toIdentityToken(clientId: ClientType) { - const obj = super.toIdentityToken(clientId); - - obj.grant_type = "password"; - obj.username = this.email; - obj.password = this.masterPasswordHash; - - if (this.newDeviceOtp) { - obj.newDeviceOtp = this.newDeviceOtp; - } - - return obj; - } - - alterIdentityTokenHeaders(headers: Headers) { - headers.set("Auth-Email", Utils.fromUtf8ToUrlB64(this.email)); - } - - static fromJSON(json: any) { - return Object.assign(Object.create(PasswordTokenRequest.prototype), json, { - device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, - twoFactor: json.twoFactor - ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) - : undefined, - }); - } -} +export { PasswordTokenRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/sso-token.request.ts b/libs/common/src/auth/models/request/identity-token/sso-token.request.ts index 97df2c6b7f2..baece0a391a 100644 --- a/libs/common/src/auth/models/request/identity-token/sso-token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/sso-token.request.ts @@ -1,35 +1 @@ -import { DeviceRequest } from "./device.request"; -import { TokenTwoFactorRequest } from "./token-two-factor.request"; -import { TokenRequest } from "./token.request"; - -export class SsoTokenRequest extends TokenRequest { - constructor( - public code: string, - public codeVerifier: string, - public redirectUri: string, - protected twoFactor: TokenTwoFactorRequest, - device?: DeviceRequest, - ) { - super(twoFactor, device); - } - - toIdentityToken(clientId: string) { - const obj = super.toIdentityToken(clientId); - - obj.grant_type = "authorization_code"; - obj.code = this.code; - obj.code_verifier = this.codeVerifier; - obj.redirect_uri = this.redirectUri; - - return obj; - } - - static fromJSON(json: any) { - return Object.assign(Object.create(SsoTokenRequest.prototype), json, { - device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, - twoFactor: json.twoFactor - ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) - : undefined, - }); - } -} +export { SsoTokenRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts b/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts index b692ddfe37c..2522b471675 100644 --- a/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts +++ b/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts @@ -1,11 +1 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { TwoFactorProviderType } from "../../../enums/two-factor-provider-type"; - -export class TokenTwoFactorRequest { - constructor( - public provider: TwoFactorProviderType = null, - public token: string = null, - public remember: boolean = false, - ) {} -} +export { TokenTwoFactorRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/token.request.ts b/libs/common/src/auth/models/request/identity-token/token.request.ts index 497038878d0..8d98c0249a6 100644 --- a/libs/common/src/auth/models/request/identity-token/token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/token.request.ts @@ -1,58 +1 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DeviceRequest } from "./device.request"; -import { TokenTwoFactorRequest } from "./token-two-factor.request"; - -export abstract class TokenRequest { - protected device?: DeviceRequest; - protected authRequest: string; - - constructor( - protected twoFactor?: TokenTwoFactorRequest, - device?: DeviceRequest, - ) { - this.device = device != null ? device : null; - } - - alterIdentityTokenHeaders(headers: Headers) { - // Implemented in subclass if required - } - - setTwoFactor(twoFactor: TokenTwoFactorRequest | undefined) { - this.twoFactor = twoFactor; - } - - setAuthRequestAccessCode(accessCode: string) { - this.authRequest = accessCode; - } - - protected toIdentityToken(clientId: string) { - const obj: any = { - scope: "api offline_access", - client_id: clientId, - }; - - if (this.device) { - obj.deviceType = this.device.type; - obj.deviceIdentifier = this.device.identifier; - obj.deviceName = this.device.name; - // no push tokens for browser apps yet - // obj.devicePushToken = this.device.pushToken; - } - - //passswordless login - if (this.authRequest) { - obj.authRequest = this.authRequest; - } - - if (this.twoFactor) { - if (this.twoFactor.token && this.twoFactor.provider != null) { - obj.twoFactorToken = this.twoFactor.token; - obj.twoFactorProvider = this.twoFactor.provider; - obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0"; - } - } - - return obj; - } -} +export { TokenRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/user-api-token.request.ts b/libs/common/src/auth/models/request/identity-token/user-api-token.request.ts index d6483582d56..387d7072ccd 100644 --- a/libs/common/src/auth/models/request/identity-token/user-api-token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/user-api-token.request.ts @@ -1,33 +1 @@ -import { DeviceRequest } from "./device.request"; -import { TokenTwoFactorRequest } from "./token-two-factor.request"; -import { TokenRequest } from "./token.request"; - -export class UserApiTokenRequest extends TokenRequest { - constructor( - public clientId: string, - public clientSecret: string, - protected twoFactor: TokenTwoFactorRequest, - device?: DeviceRequest, - ) { - super(twoFactor, device); - } - - toIdentityToken() { - const obj = super.toIdentityToken(this.clientId); - - obj.scope = this.clientId.startsWith("organization") ? "api.organization" : "api"; - obj.grant_type = "client_credentials"; - obj.client_secret = this.clientSecret; - - return obj; - } - - static fromJSON(json: any) { - return Object.assign(Object.create(UserApiTokenRequest.prototype), json, { - device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, - twoFactor: json.twoFactor - ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) - : undefined, - }); - } -} +export { UserApiTokenRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/request/identity-token/webauthn-login-token.request.ts b/libs/common/src/auth/models/request/identity-token/webauthn-login-token.request.ts index 66cc96c9c7a..33197cebc7e 100644 --- a/libs/common/src/auth/models/request/identity-token/webauthn-login-token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/webauthn-login-token.request.ts @@ -1,36 +1 @@ -import { WebAuthnLoginAssertionResponseRequest } from "../../../services/webauthn-login/request/webauthn-login-assertion-response.request"; - -import { DeviceRequest } from "./device.request"; -import { TokenTwoFactorRequest } from "./token-two-factor.request"; -import { TokenRequest } from "./token.request"; - -export class WebAuthnLoginTokenRequest extends TokenRequest { - constructor( - public token: string, - public deviceResponse: WebAuthnLoginAssertionResponseRequest, - device?: DeviceRequest, - ) { - super(undefined, device); - } - - toIdentityToken(clientId: string) { - const obj = super.toIdentityToken(clientId); - - obj.grant_type = "webauthn"; - obj.token = this.token; - // must be a string b/c sending as form encoded data - obj.deviceResponse = JSON.stringify(this.deviceResponse); - - return obj; - } - - static fromJSON(json: any) { - return Object.assign(Object.create(WebAuthnLoginTokenRequest.prototype), json, { - deviceResponse: WebAuthnLoginAssertionResponseRequest.fromJSON(json.deviceResponse), - device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, - twoFactor: json.twoFactor - ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) - : undefined, - }); - } -} +export { WebAuthnLoginTokenRequest } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/response/identity-device-verification.response.ts b/libs/common/src/auth/models/response/identity-device-verification.response.ts index ac2ab62c474..102f87b2918 100644 --- a/libs/common/src/auth/models/response/identity-device-verification.response.ts +++ b/libs/common/src/auth/models/response/identity-device-verification.response.ts @@ -1,10 +1 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; - -export class IdentityDeviceVerificationResponse extends BaseResponse { - deviceVerified: boolean; - - constructor(response: any) { - super(response); - this.deviceVerified = this.getResponseProperty("DeviceVerified") ?? false; - } -} +export { IdentityDeviceVerificationResponse } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 53242a25b21..0a1214e20ec 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -1,69 +1 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { KdfType } from "@bitwarden/key-management"; - -import { EncString } from "../../../key-management/crypto/models/enc-string"; -import { BaseResponse } from "../../../models/response/base.response"; - -import { MasterPasswordPolicyResponse } from "./master-password-policy.response"; -import { UserDecryptionOptionsResponse } from "./user-decryption-options/user-decryption-options.response"; - -export class IdentityTokenResponse extends BaseResponse { - accessToken: string; - expiresIn: number; - refreshToken: string; - tokenType: string; - - resetMasterPassword: boolean; - privateKey: string; // userKeyEncryptedPrivateKey - key?: EncString; // masterKeyEncryptedUserKey - twoFactorToken: string; - kdf: KdfType; - kdfIterations: number; - kdfMemory?: number; - kdfParallelism?: number; - forcePasswordReset: boolean; - masterPasswordPolicy: MasterPasswordPolicyResponse; - apiUseKeyConnector: boolean; - keyConnectorUrl: string; - - userDecryptionOptions: UserDecryptionOptionsResponse; - - constructor(response: any) { - super(response); - this.accessToken = response.access_token; - this.expiresIn = response.expires_in; - this.refreshToken = response.refresh_token; - this.tokenType = response.token_type; - - this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword"); - this.privateKey = this.getResponseProperty("PrivateKey"); - const key = this.getResponseProperty("Key"); - if (key) { - this.key = new EncString(key); - } - this.twoFactorToken = this.getResponseProperty("TwoFactorToken"); - this.kdf = this.getResponseProperty("Kdf"); - this.kdfIterations = this.getResponseProperty("KdfIterations"); - this.kdfMemory = this.getResponseProperty("KdfMemory"); - this.kdfParallelism = this.getResponseProperty("KdfParallelism"); - this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset"); - this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector"); - this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); - this.masterPasswordPolicy = new MasterPasswordPolicyResponse( - this.getResponseProperty("MasterPasswordPolicy"), - ); - - if (response.UserDecryptionOptions) { - this.userDecryptionOptions = new UserDecryptionOptionsResponse( - this.getResponseProperty("UserDecryptionOptions"), - ); - } - } - - hasMasterKeyEncryptedUserKey(): boolean { - return Boolean(this.key); - } -} +export { IdentityTokenResponse } from "@bitwarden/token-provider"; diff --git a/libs/common/src/auth/models/response/identity-two-factor.response.ts b/libs/common/src/auth/models/response/identity-two-factor.response.ts index b52fbcb8771..949ea9be9ae 100644 --- a/libs/common/src/auth/models/response/identity-two-factor.response.ts +++ b/libs/common/src/auth/models/response/identity-two-factor.response.ts @@ -1,26 +1 @@ -import { BaseResponse } from "../../../models/response/base.response"; -import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; - -import { MasterPasswordPolicyResponse } from "./master-password-policy.response"; - -export class IdentityTwoFactorResponse extends BaseResponse { - // contains available two-factor providers - twoFactorProviders: TwoFactorProviderType[]; - // a map of two-factor providers to necessary data for completion - twoFactorProviders2: Record>; - ssoEmail2faSessionToken: string; - email?: string; - masterPasswordPolicy?: MasterPasswordPolicyResponse; - - constructor(response: any) { - super(response); - this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders"); - this.twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2"); - this.masterPasswordPolicy = new MasterPasswordPolicyResponse( - this.getResponseProperty("MasterPasswordPolicy"), - ); - - this.ssoEmail2faSessionToken = this.getResponseProperty("SsoEmail2faSessionToken"); - this.email = this.getResponseProperty("Email"); - } -} +export { IdentityTwoFactorResponse } from "@bitwarden/token-provider"; diff --git a/libs/token-provider/README.md b/libs/token-provider/README.md new file mode 100644 index 00000000000..17eb2b4e648 --- /dev/null +++ b/libs/token-provider/README.md @@ -0,0 +1,5 @@ +# token-provider + +Owned by: auth + +Auth token provider (Identity and API token flows) diff --git a/libs/token-provider/eslint.config.mjs b/libs/token-provider/eslint.config.mjs new file mode 100644 index 00000000000..9c37d10e3ff --- /dev/null +++ b/libs/token-provider/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from "../../eslint.config.mjs"; + +export default [...baseConfig]; diff --git a/libs/token-provider/jest.config.js b/libs/token-provider/jest.config.js new file mode 100644 index 00000000000..0f7d7b5b07c --- /dev/null +++ b/libs/token-provider/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + displayName: "token-provider", + preset: "../../jest.preset.js", + testEnvironment: "node", + transform: { + "^.+\\.[tj]s$": ["ts-jest", { tsconfig: "/tsconfig.spec.json" }], + }, + moduleFileExtensions: ["ts", "js", "html"], + coverageDirectory: "../../coverage/libs/token-provider", +}; diff --git a/libs/token-provider/package.json b/libs/token-provider/package.json new file mode 100644 index 00000000000..e33b7ff09b8 --- /dev/null +++ b/libs/token-provider/package.json @@ -0,0 +1,11 @@ +{ + "name": "@bitwarden/token-provider", + "version": "0.0.1", + "description": "Auth token provider (Identity and API token flows)", + "private": true, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "GPL-3.0", + "author": "auth" +} diff --git a/libs/token-provider/project.json b/libs/token-provider/project.json new file mode 100644 index 00000000000..aa5066c852f --- /dev/null +++ b/libs/token-provider/project.json @@ -0,0 +1,33 @@ +{ + "name": "token-provider", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/token-provider/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/token-provider", + "main": "libs/token-provider/src/index.ts", + "tsConfig": "libs/token-provider/tsconfig.lib.json", + "assets": ["libs/token-provider/*.md"] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/token-provider/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/token-provider/jest.config.js" + } + } + } +} diff --git a/libs/token-provider/src/default-token-api.service.ts b/libs/token-provider/src/default-token-api.service.ts new file mode 100644 index 00000000000..f634435a2ab --- /dev/null +++ b/libs/token-provider/src/default-token-api.service.ts @@ -0,0 +1,339 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { firstValueFrom } from "rxjs"; + +import { LogService } from "@bitwarden/logging"; +import { PlatformUtilsService } from "@bitwarden/platform-utils"; + +import { LogoutReason } from "@bitwarden/auth/common"; +import { DeviceRequest } from "./device.request"; +import { PasswordTokenRequest } from "./password-token.request"; +import { SsoTokenRequest } from "./sso-token.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; +import { UserApiTokenRequest } from "./user-api-token.request"; +import { WebAuthnLoginTokenRequest } from "./webauthn-login-token.request"; +import { IdentityDeviceVerificationResponse } from "./identity-device-verification.response"; +import { IdentityTokenResponse } from "./identity-token.response"; +import { IdentityTwoFactorResponse } from "./identity-two-factor.response"; + +import { DeviceType } from "@bitwarden/device-type"; +import { ClientType } from "@bitwarden/client-type"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; +import { VaultTimeoutAction } from "@bitwarden/common/key-management/vault-timeout/enums/vault-timeout-action.enum"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { flagEnabled } from "@bitwarden/common/platform/misc/flags"; + +import { TokenApiService } from "./token-api.service"; + +export type HttpOperations = { + createRequest: (url: string, request: RequestInit) => Request; +}; + +export class DefaultTokenApiService implements TokenApiService { + private device: DeviceType; + private deviceType: string; + private refreshTokenPromise: Promise | undefined; + + /** + * The message (responseJson.ErrorModel.Message) that comes back from the server when a new device verification is required. + */ + private static readonly NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE = + "new device verification required"; + + constructor( + private platformUtilsService: PlatformUtilsService, + private environmentService: EnvironmentService, + private appIdService: AppIdService, + private refreshAccessTokenErrorCallback: () => void, + private logService: LogService, + private logoutCallback: (logoutReason: LogoutReason) => Promise, + private vaultTimeoutSettingsService: VaultTimeoutSettingsService, + private customUserAgent: string = null, + ) { + this.device = platformUtilsService.getDevice(); + this.deviceType = this.device.toString(); + } + + async postIdentityToken( + request: + | UserApiTokenRequest + | PasswordTokenRequest + | SsoTokenRequest + | WebAuthnLoginTokenRequest, + ): Promise< + IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse + > { + const headers = new Headers({ + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + Accept: "application/json", + "Device-Type": this.deviceType, + }); + if (flagEnabled("prereleaseBuild")) { + headers.set("Is-Prerelease", "1"); + } + if (flagEnabled("prereleaseBuild")) { + headers.set("Is-Prerelease", "1"); + } + if (this.customUserAgent != null) { + headers.set("User-Agent", this.customUserAgent); + } + request.alterIdentityTokenHeaders(headers); + + const identityToken = + request instanceof UserApiTokenRequest + ? request.toIdentityToken() + : request.toIdentityToken(this.platformUtilsService.getClientType()); + + const env = await firstValueFrom(this.environmentService.environment$); + + const response = await this.fetch( + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { + body: this.qsStringify(identityToken), + credentials: await this.getCredentials(), + cache: "no-store", + headers: headers, + method: "POST", + }), + ); + + let responseJson: any = null; + if (this.isJsonResponse(response)) { + responseJson = await response.json(); + } + + if (responseJson != null) { + if (response.status === 200) { + return new IdentityTokenResponse(responseJson); + } else if ( + response.status === 400 && + responseJson.TwoFactorProviders2 && + Object.keys(responseJson.TwoFactorProviders2).length + ) { + return new IdentityTwoFactorResponse(responseJson); + } else if ( + response.status === 400 && + responseJson?.ErrorModel?.Message === + DefaultTokenApiService.NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE + ) { + return new IdentityDeviceVerificationResponse(responseJson); + } + } + + return Promise.reject(new ErrorResponse(responseJson, response.status, true)); + } + + async refreshIdentityToken(): Promise { + try { + await this.refreshToken(); + } catch (e) { + this.logService.error("Error refreshing access token: ", e); + throw e; + } + } + + async getActiveBearerToken(): Promise { + let accessToken = await this.tokenService.getAccessToken(); + if (await this.tokenService.tokenNeedsRefresh()) { + accessToken = await this.refreshToken(); + } + return accessToken; + } + + async fetch(request: Request): Promise { + if (request.method === "GET") { + request.headers.set("Cache-Control", "no-store"); + request.headers.set("Pragma", "no-cache"); + } + request.headers.set("Bitwarden-Client-Name", this.platformUtilsService.getClientType()); + request.headers.set( + "Bitwarden-Client-Version", + await this.platformUtilsService.getApplicationVersionNumber(), + ); + return this.nativeFetch(request); + } + + nativeFetch(request: Request): Promise { + return fetch(request); + } + + private async handleError( + response: Response, + tokenError: boolean, + authed: boolean, + ): Promise { + let responseJson: any = null; + if (this.isJsonResponse(response)) { + responseJson = await response.json(); + } else if (this.isTextPlainResponse(response)) { + responseJson = { Message: await response.text() }; + } + + if (authed) { + if ( + response.status === 401 || + response.status === 403 || + (tokenError && + response.status === 400 && + responseJson != null && + responseJson.error === "invalid_grant") + ) { + await this.logoutCallback("invalidGrantError"); + } + } + + return new ErrorResponse(responseJson, response.status, tokenError); + } + + private qsStringify(params: any): string { + return Object.keys(params) + .map((key) => { + return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + }) + .join("&"); + } + + private async getCredentials(): Promise { + const env = await firstValueFrom(this.environmentService.environment$); + if (this.platformUtilsService.getClientType() !== ClientType.Web || env.hasBaseUrl()) { + return "include"; + } + return undefined; + } + + private isJsonResponse(response: Response): boolean { + const typeHeader = response.headers.get("content-type"); + return typeHeader != null && typeHeader.indexOf("application/json") > -1; + } + + private isTextPlainResponse(response: Response): boolean { + const typeHeader = response.headers.get("content-type"); + return typeHeader != null && typeHeader.indexOf("text/plain") > -1; + } + + // Token refresh helpers (lift-and-shift parity with ApiService) + private async internalRefreshToken(): Promise { + const refreshToken = await this.tokenService.getRefreshToken(); + if (refreshToken != null && refreshToken !== "") { + return this.refreshAccessToken(); + } + + const clientId = await this.tokenService.getClientId(); + const clientSecret = await this.tokenService.getClientSecret(); + if (!clientId || !clientSecret) { + // fall through + } else { + return this.refreshApiToken(); + } + + this.refreshAccessTokenErrorCallback(); + + throw new Error("Cannot refresh access token, no refresh token or api keys are stored."); + } + + protected refreshToken(): Promise { + if (this.refreshTokenPromise === undefined) { + this.refreshTokenPromise = this.internalRefreshToken(); + void this.refreshTokenPromise.finally(() => { + this.refreshTokenPromise = undefined; + }); + } + return this.refreshTokenPromise; + } + + protected async refreshAccessToken(): 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 env = await firstValueFrom(this.environmentService.environment$); + const decodedToken = await this.tokenService.decodeAccessToken(); + const response = await this.fetch( + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { + body: this.qsStringify({ + grant_type: "refresh_token", + client_id: decodedToken.client_id, + refresh_token: refreshToken, + }), + cache: "no-store", + credentials: await this.getCredentials(), + headers: headers, + method: "POST", + }), + ); + + if (response.status === 200) { + const responseJson = await response.json(); + const tokenResponse = new IdentityTokenResponse(responseJson); + + const newDecodedAccessToken = await this.tokenService.decodeAccessToken( + tokenResponse.accessToken, + ); + const userId = newDecodedAccessToken.sub; + + const vaultTimeoutAction = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId), + ); + const vaultTimeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId), + ); + + const refreshedTokens = await this.tokenService.setTokens( + tokenResponse.accessToken, + vaultTimeoutAction as VaultTimeoutAction, + vaultTimeout, + tokenResponse.refreshToken, + ); + return refreshedTokens.accessToken; + } else { + const error = await this.handleError(response, true, true); + return Promise.reject(error); + } + } + + protected async refreshApiToken(): Promise { + const clientId = await this.tokenService.getClientId(); + const clientSecret = await this.tokenService.getClientSecret(); + + const appId = await this.appIdService.getAppId(); + const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); + const tokenRequest = new UserApiTokenRequest( + clientId, + clientSecret, + new TokenTwoFactorRequest(), + deviceRequest, + ); + + const response = await this.postIdentityToken(tokenRequest); + if (!(response instanceof IdentityTokenResponse)) { + throw new Error("Invalid response received when refreshing api token"); + } + + const newDecodedAccessToken = await this.tokenService.decodeAccessToken(response.accessToken); + const userId = newDecodedAccessToken.sub; + + const vaultTimeoutAction = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId), + ); + const vaultTimeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId), + ); + + const refreshedToken = await this.tokenService.setAccessToken( + response.accessToken, + vaultTimeoutAction as VaultTimeoutAction, + vaultTimeout, + ); + return refreshedToken; + } +} diff --git a/libs/token-provider/src/device.request.ts b/libs/token-provider/src/device.request.ts index 5b7df6c9f4b..55a8b4867e9 100644 --- a/libs/token-provider/src/device.request.ts +++ b/libs/token-provider/src/device.request.ts @@ -3,8 +3,7 @@ import { Jsonify } from "type-fest"; import { DeviceType } from "@bitwarden/device-type"; - -import { PlatformUtilsService } from "../../../../platform/abstractions/platform-utils.service"; +import { PlatformUtilsService } from "@bitwarden/platform-utils"; export class DeviceRequest { type: DeviceType; diff --git a/libs/token-provider/src/identity-device-verification.response.ts b/libs/token-provider/src/identity-device-verification.response.ts new file mode 100644 index 00000000000..ac2ab62c474 --- /dev/null +++ b/libs/token-provider/src/identity-device-verification.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class IdentityDeviceVerificationResponse extends BaseResponse { + deviceVerified: boolean; + + constructor(response: any) { + super(response); + this.deviceVerified = this.getResponseProperty("DeviceVerified") ?? false; + } +} diff --git a/libs/token-provider/src/identity-token.response.ts b/libs/token-provider/src/identity-token.response.ts new file mode 100644 index 00000000000..c4467a96f93 --- /dev/null +++ b/libs/token-provider/src/identity-token.response.ts @@ -0,0 +1,67 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. + +import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; +import { UserDecryptionOptionsResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; +import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { KdfType } from "@bitwarden/key-management"; + +export class IdentityTokenResponse extends BaseResponse { + accessToken: string; + expiresIn: number; + refreshToken: string; + tokenType: string; + + resetMasterPassword: boolean; + privateKey: string; // userKeyEncryptedPrivateKey + key?: EncString; // masterKeyEncryptedUserKey + twoFactorToken: string; + kdf: KdfType; + kdfIterations: number; + kdfMemory?: number; + kdfParallelism?: number; + forcePasswordReset: boolean; + masterPasswordPolicy: MasterPasswordPolicyResponse; + apiUseKeyConnector: boolean; + keyConnectorUrl: string; + + userDecryptionOptions: UserDecryptionOptionsResponse; + + constructor(response: any) { + super(response); + this.accessToken = response.access_token; + this.expiresIn = response.expires_in; + this.refreshToken = response.refresh_token; + this.tokenType = response.token_type; + + this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword"); + this.privateKey = this.getResponseProperty("PrivateKey"); + const key = this.getResponseProperty("Key"); + if (key) { + this.key = new EncString(key); + } + this.twoFactorToken = this.getResponseProperty("TwoFactorToken"); + this.kdf = this.getResponseProperty("Kdf"); + this.kdfIterations = this.getResponseProperty("KdfIterations"); + this.kdfMemory = this.getResponseProperty("KdfMemory"); + this.kdfParallelism = this.getResponseProperty("KdfParallelism"); + this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset"); + this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector"); + this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); + this.masterPasswordPolicy = new MasterPasswordPolicyResponse( + this.getResponseProperty("MasterPasswordPolicy"), + ); + + if (response.UserDecryptionOptions) { + this.userDecryptionOptions = new UserDecryptionOptionsResponse( + this.getResponseProperty("UserDecryptionOptions"), + ); + } + } + + hasMasterKeyEncryptedUserKey(): boolean { + return Boolean(this.key); + } +} diff --git a/libs/token-provider/src/identity-two-factor.response.ts b/libs/token-provider/src/identity-two-factor.response.ts new file mode 100644 index 00000000000..16258912e81 --- /dev/null +++ b/libs/token-provider/src/identity-two-factor.response.ts @@ -0,0 +1,25 @@ +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class IdentityTwoFactorResponse extends BaseResponse { + // contains available two-factor providers + twoFactorProviders: TwoFactorProviderType[]; + // a map of two-factor providers to necessary data for completion + twoFactorProviders2: Record>; + ssoEmail2faSessionToken: string; + email?: string; + masterPasswordPolicy?: MasterPasswordPolicyResponse; + + constructor(response: any) { + super(response); + this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders"); + this.twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2"); + this.masterPasswordPolicy = new MasterPasswordPolicyResponse( + this.getResponseProperty("MasterPasswordPolicy"), + ); + + this.ssoEmail2faSessionToken = this.getResponseProperty("SsoEmail2faSessionToken"); + this.email = this.getResponseProperty("Email"); + } +} diff --git a/libs/token-provider/src/index.ts b/libs/token-provider/src/index.ts new file mode 100644 index 00000000000..e04e5aa6bc5 --- /dev/null +++ b/libs/token-provider/src/index.ts @@ -0,0 +1,5 @@ +export * from "./token-api.service"; +export * from "./default-token-api.service"; +export * from "./identity-token.response"; +export * from "./identity-two-factor.response"; +export * from "./identity-device-verification.response"; diff --git a/libs/token-provider/src/password-token.request.ts b/libs/token-provider/src/password-token.request.ts new file mode 100644 index 00000000000..709987c32a7 --- /dev/null +++ b/libs/token-provider/src/password-token.request.ts @@ -0,0 +1,45 @@ +import { ClientType } from "@bitwarden/client-type"; +import { fromUtf8ToUrlB64 } from "@bitwarden/encoding"; + +import { DeviceRequest } from "./device.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; +import { TokenRequest } from "./token.request"; + +export class PasswordTokenRequest extends TokenRequest { + constructor( + public email: string, + public masterPasswordHash: string, + protected twoFactor: TokenTwoFactorRequest, + device?: DeviceRequest, + public newDeviceOtp?: string, + ) { + super(twoFactor, device); + } + + toIdentityToken(clientId: ClientType) { + const obj = super.toIdentityToken(clientId); + + obj.grant_type = "password"; + obj.username = this.email; + obj.password = this.masterPasswordHash; + + if (this.newDeviceOtp) { + obj.newDeviceOtp = this.newDeviceOtp; + } + + return obj; + } + + alterIdentityTokenHeaders(headers: Headers) { + headers.set("Auth-Email", fromUtf8ToUrlB64(this.email) ?? ""); + } + + static fromJSON(json: any) { + return Object.assign(Object.create(PasswordTokenRequest.prototype), json, { + device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, + twoFactor: json.twoFactor + ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) + : undefined, + }); + } +} diff --git a/libs/token-provider/src/sso-token.request.ts b/libs/token-provider/src/sso-token.request.ts new file mode 100644 index 00000000000..97df2c6b7f2 --- /dev/null +++ b/libs/token-provider/src/sso-token.request.ts @@ -0,0 +1,35 @@ +import { DeviceRequest } from "./device.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; +import { TokenRequest } from "./token.request"; + +export class SsoTokenRequest extends TokenRequest { + constructor( + public code: string, + public codeVerifier: string, + public redirectUri: string, + protected twoFactor: TokenTwoFactorRequest, + device?: DeviceRequest, + ) { + super(twoFactor, device); + } + + toIdentityToken(clientId: string) { + const obj = super.toIdentityToken(clientId); + + obj.grant_type = "authorization_code"; + obj.code = this.code; + obj.code_verifier = this.codeVerifier; + obj.redirect_uri = this.redirectUri; + + return obj; + } + + static fromJSON(json: any) { + return Object.assign(Object.create(SsoTokenRequest.prototype), json, { + device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, + twoFactor: json.twoFactor + ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) + : undefined, + }); + } +} diff --git a/libs/token-provider/src/token-api.service.ts b/libs/token-provider/src/token-api.service.ts new file mode 100644 index 00000000000..f18bf735ab4 --- /dev/null +++ b/libs/token-provider/src/token-api.service.ts @@ -0,0 +1,21 @@ +import { IdentityDeviceVerificationResponse } from "./identity-device-verification.response"; +import { IdentityTokenResponse } from "./identity-token.response"; +import { IdentityTwoFactorResponse } from "./identity-two-factor.response"; +import { PasswordTokenRequest } from "./password-token.request"; +import { SsoTokenRequest } from "./sso-token.request"; +import { UserApiTokenRequest } from "./user-api-token.request"; +import { WebAuthnLoginTokenRequest } from "./webauthn-login-token.request"; + +export abstract class TokenApiService { + abstract postIdentityToken( + request: + | UserApiTokenRequest + | PasswordTokenRequest + | SsoTokenRequest + | WebAuthnLoginTokenRequest, + ): Promise< + IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse + >; + abstract refreshIdentityToken(): Promise; + abstract getActiveBearerToken(): Promise; +} diff --git a/libs/token-provider/src/token-provider.spec.ts b/libs/token-provider/src/token-provider.spec.ts new file mode 100644 index 00000000000..8ea9840d357 --- /dev/null +++ b/libs/token-provider/src/token-provider.spec.ts @@ -0,0 +1,8 @@ +import * as lib from "./index"; + +describe("token-provider", () => { + // This test will fail until something is exported from index.ts + it("should work", () => { + expect(lib).toBeDefined(); + }); +}); diff --git a/libs/token-provider/src/token-two-factor.request.ts b/libs/token-provider/src/token-two-factor.request.ts new file mode 100644 index 00000000000..b692ddfe37c --- /dev/null +++ b/libs/token-provider/src/token-two-factor.request.ts @@ -0,0 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { TwoFactorProviderType } from "../../../enums/two-factor-provider-type"; + +export class TokenTwoFactorRequest { + constructor( + public provider: TwoFactorProviderType = null, + public token: string = null, + public remember: boolean = false, + ) {} +} diff --git a/libs/token-provider/src/token.request.ts b/libs/token-provider/src/token.request.ts new file mode 100644 index 00000000000..497038878d0 --- /dev/null +++ b/libs/token-provider/src/token.request.ts @@ -0,0 +1,58 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { DeviceRequest } from "./device.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; + +export abstract class TokenRequest { + protected device?: DeviceRequest; + protected authRequest: string; + + constructor( + protected twoFactor?: TokenTwoFactorRequest, + device?: DeviceRequest, + ) { + this.device = device != null ? device : null; + } + + alterIdentityTokenHeaders(headers: Headers) { + // Implemented in subclass if required + } + + setTwoFactor(twoFactor: TokenTwoFactorRequest | undefined) { + this.twoFactor = twoFactor; + } + + setAuthRequestAccessCode(accessCode: string) { + this.authRequest = accessCode; + } + + protected toIdentityToken(clientId: string) { + const obj: any = { + scope: "api offline_access", + client_id: clientId, + }; + + if (this.device) { + obj.deviceType = this.device.type; + obj.deviceIdentifier = this.device.identifier; + obj.deviceName = this.device.name; + // no push tokens for browser apps yet + // obj.devicePushToken = this.device.pushToken; + } + + //passswordless login + if (this.authRequest) { + obj.authRequest = this.authRequest; + } + + if (this.twoFactor) { + if (this.twoFactor.token && this.twoFactor.provider != null) { + obj.twoFactorToken = this.twoFactor.token; + obj.twoFactorProvider = this.twoFactor.provider; + obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0"; + } + } + + return obj; + } +} diff --git a/libs/token-provider/src/user-api-token.request.ts b/libs/token-provider/src/user-api-token.request.ts new file mode 100644 index 00000000000..d6483582d56 --- /dev/null +++ b/libs/token-provider/src/user-api-token.request.ts @@ -0,0 +1,33 @@ +import { DeviceRequest } from "./device.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; +import { TokenRequest } from "./token.request"; + +export class UserApiTokenRequest extends TokenRequest { + constructor( + public clientId: string, + public clientSecret: string, + protected twoFactor: TokenTwoFactorRequest, + device?: DeviceRequest, + ) { + super(twoFactor, device); + } + + toIdentityToken() { + const obj = super.toIdentityToken(this.clientId); + + obj.scope = this.clientId.startsWith("organization") ? "api.organization" : "api"; + obj.grant_type = "client_credentials"; + obj.client_secret = this.clientSecret; + + return obj; + } + + static fromJSON(json: any) { + return Object.assign(Object.create(UserApiTokenRequest.prototype), json, { + device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, + twoFactor: json.twoFactor + ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) + : undefined, + }); + } +} diff --git a/libs/token-provider/src/webauthn-login-token.request.ts b/libs/token-provider/src/webauthn-login-token.request.ts new file mode 100644 index 00000000000..66cc96c9c7a --- /dev/null +++ b/libs/token-provider/src/webauthn-login-token.request.ts @@ -0,0 +1,36 @@ +import { WebAuthnLoginAssertionResponseRequest } from "../../../services/webauthn-login/request/webauthn-login-assertion-response.request"; + +import { DeviceRequest } from "./device.request"; +import { TokenTwoFactorRequest } from "./token-two-factor.request"; +import { TokenRequest } from "./token.request"; + +export class WebAuthnLoginTokenRequest extends TokenRequest { + constructor( + public token: string, + public deviceResponse: WebAuthnLoginAssertionResponseRequest, + device?: DeviceRequest, + ) { + super(undefined, device); + } + + toIdentityToken(clientId: string) { + const obj = super.toIdentityToken(clientId); + + obj.grant_type = "webauthn"; + obj.token = this.token; + // must be a string b/c sending as form encoded data + obj.deviceResponse = JSON.stringify(this.deviceResponse); + + return obj; + } + + static fromJSON(json: any) { + return Object.assign(Object.create(WebAuthnLoginTokenRequest.prototype), json, { + deviceResponse: WebAuthnLoginAssertionResponseRequest.fromJSON(json.deviceResponse), + device: json.device ? DeviceRequest.fromJSON(json.device) : undefined, + twoFactor: json.twoFactor + ? Object.assign(new TokenTwoFactorRequest(), json.twoFactor) + : undefined, + }); + } +} diff --git a/libs/token-provider/tsconfig.eslint.json b/libs/token-provider/tsconfig.eslint.json new file mode 100644 index 00000000000..3daf120441a --- /dev/null +++ b/libs/token-provider/tsconfig.eslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": ["src/**/*.ts", "src/**/*.js"], + "exclude": ["**/build", "**/dist"] +} diff --git a/libs/token-provider/tsconfig.json b/libs/token-provider/tsconfig.json new file mode 100644 index 00000000000..62ebbd94647 --- /dev/null +++ b/libs/token-provider/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/token-provider/tsconfig.lib.json b/libs/token-provider/tsconfig.lib.json new file mode 100644 index 00000000000..9cbf6736007 --- /dev/null +++ b/libs/token-provider/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.js", "src/**/*.spec.ts"] +} diff --git a/libs/token-provider/tsconfig.spec.json b/libs/token-provider/tsconfig.spec.json new file mode 100644 index 00000000000..1275f148a18 --- /dev/null +++ b/libs/token-provider/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "moduleResolution": "node10", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/package-lock.json b/package-lock.json index 914af85c221..00ffd6e2297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -432,6 +432,11 @@ "version": "0.0.1", "license": "GPL-3.0" }, + "libs/token-provider": { + "name": "@bitwarden/token-provider", + "version": "0.0.1", + "license": "GPL-3.0" + }, "libs/tools/export/vault-export/vault-export-core": { "name": "@bitwarden/vault-export-core", "version": "0.0.0", @@ -4735,6 +4740,10 @@ "resolved": "libs/storage-test-utils", "link": true }, + "node_modules/@bitwarden/token-provider": { + "resolved": "libs/token-provider", + "link": true + }, "node_modules/@bitwarden/ui-common": { "resolved": "libs/ui/common", "link": true diff --git a/tsconfig.base.json b/tsconfig.base.json index 26a5a041ca3..d1b4c1ed642 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -56,6 +56,7 @@ "@bitwarden/state-test-utils": ["libs/state-test-utils/src/index.ts"], "@bitwarden/storage-core": ["libs/storage-core/src/index.ts"], "@bitwarden/storage-test-utils": ["libs/storage-test-utils/src/index.ts"], + "@bitwarden/token-provider": ["libs/token-provider/src/index.ts"], "@bitwarden/ui-common": ["./libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], "@bitwarden/user-core": ["libs/user-core/src/index.ts"],