diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index a8ce05f180e..e1c74a70943 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -36,7 +36,6 @@ ./libs/angular/src/interfaces/selectOptions.ts ./libs/components/src/stories/Introduction.stories.mdx ./libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts -./libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts ./libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts ./libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts ./libs/common/spec/web/services/webCryptoFunction.service.spec.ts @@ -60,7 +59,6 @@ ./libs/common/spec/services/consoleLog.service.spec.ts ./libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts ./libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts -./libs/common/src/misc/logInStrategies/apiLogin.strategy.ts ./libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts ./libs/common/src/misc/logInStrategies/logIn.strategy.ts ./libs/common/src/misc/nodeUtils.ts @@ -165,7 +163,6 @@ ./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/interfaces/IEncrypted.ts ./libs/node/spec/cli/consoleLog.service.spec.ts -./libs/node/spec/services/nodeCryptoFunction.service.spec.ts ./libs/node/src/cli/models/response/baseResponse.ts ./libs/node/src/cli/models/response/stringResponse.ts ./libs/node/src/cli/models/response/fileResponse.ts @@ -175,7 +172,6 @@ ./libs/node/src/cli/services/consoleLog.service.ts ./libs/node/src/cli/services/cliPlatformUtils.service.ts ./libs/node/src/services/nodeApi.service.ts -./libs/node/src/services/nodeCryptoFunction.service.ts ./libs/node/src/services/lowdbStorage.service.ts ./libs/electron/spec/services/electronLog.service.spec.ts ./libs/electron/src/baseMenu.ts diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 3dc4085d3d0..e9224a8919a 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -6,6 +6,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const { AngularWebpackPlugin } = require("@ngtools/webpack"); const TerserPlugin = require("terser-webpack-plugin"); +const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin"); const configurator = require("./config/config"); if (process.env.NODE_ENV == null) { @@ -67,17 +68,24 @@ const moduleRules = [ }, ]; +const requiredPlugins = [ + new webpack.DefinePlugin({ + "process.env": { + ENV: JSON.stringify(ENV), + }, + }), + new webpack.EnvironmentPlugin({ + FLAGS: envConfig.flags, + DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, + }), +]; + const plugins = [ new HtmlWebpackPlugin({ template: "./src/popup/index.html", filename: "popup/index.html", chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], }), - new HtmlWebpackPlugin({ - template: "./src/background.html", - filename: "background.html", - chunks: ["vendor", "background"], - }), new HtmlWebpackPlugin({ template: "./src/notification/bar.html", filename: "notification/bar.html", @@ -99,11 +107,6 @@ const plugins = [ filename: "[name].css", chunkFilename: "chunk-[id].css", }), - new webpack.DefinePlugin({ - "process.env": { - ENV: JSON.stringify(ENV), - }, - }), new AngularWebpackPlugin({ tsConfigPath: "tsconfig.json", entryModule: "src/popup/app.module#AppModule", @@ -119,19 +122,20 @@ const plugins = [ exclude: [/content\/.*/, /notification\/.*/], filename: "[file].map", }), - new webpack.EnvironmentPlugin({ - FLAGS: envConfig.flags, - DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, - }), + ...requiredPlugins, ]; -const config = { +/** + * @type {import("webpack").Configuration} + * This config compiles everything but the background + */ +const mainConfig = { + name: "main", mode: ENV, devtool: false, entry: { "popup/polyfills": "./src/popup/polyfills.ts", "popup/main": "./src/popup/main.ts", - background: "./src/background.ts", "content/autofill": "./src/content/autofill.js", "content/autofiller": "./src/content/autofiller.ts", "content/notificationBar": "./src/content/notificationBar.ts", @@ -209,18 +213,72 @@ const config = { plugins: plugins, }; +/** + * @type {import("webpack").Configuration[]} + */ +const configs = []; + if (manifestVersion == 2) { - // We can't use this in manifest v3 - // Ideally we understand why this breaks it and we don't have to do this - config.optimization.splitChunks.cacheGroups.commons2 = { + mainConfig.optimization.splitChunks.cacheGroups.commons2 = { test: /[\\/]node_modules[\\/]/, name: "vendor", chunks: (chunk) => { return chunk.name === "background"; }, }; + + // Manifest V2 uses Background Pages which requires a html page. + mainConfig.plugins.push( + new HtmlWebpackPlugin({ + template: "./src/background.html", + filename: "background.html", + chunks: ["vendor", "background"], + }) + ); + + // Manifest V2 background pages can be run through the regular build pipeline. + // Since it's a standard webpage. + mainConfig.entry.background = "./src/background.ts"; + + configs.push(mainConfig); } else { - config.entry["content/misc-utils"] = "./src/content/misc-utils.ts"; + // Manifest v3 needs an extra helper for utilities in the content script. + // The javascript output of this should be added to manifest.v3.json + mainConfig.entry["content/misc-utils"] = "./src/content/misc-utils.ts"; + + /** + * @type {import("webpack").Configuration} + */ + const backgroundConfig = { + name: "background", + mode: ENV, + devtool: false, + entry: "./src/background.ts", + target: "webworker", + output: { + filename: "background.js", + path: path.resolve(__dirname, "build"), + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + }, + ], + }, + resolve: { + extensions: [".ts", ".js"], + symlinks: false, + modules: [path.resolve("../../node_modules")], + plugins: [new TsconfigPathsPlugin()], + }, + dependencies: ["main"], + plugins: [...requiredPlugins], + }; + + configs.push(mainConfig); + configs.push(backgroundConfig); } -module.exports = config; +module.exports = configs; diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 42738b7dfa7..7efee8eb3e5 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -51,8 +51,8 @@ import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vau import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; import { NodeApiService } from "@bitwarden/node/services/nodeApi.service"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; import { Program } from "./program"; import { SendProgram } from "./send.program"; diff --git a/apps/cli/src/commands/delete.command.ts b/apps/cli/src/commands/delete.command.ts index 9a4480df687..5866e2a6fd6 100644 --- a/apps/cli/src/commands/delete.command.ts +++ b/apps/cli/src/commands/delete.command.ts @@ -88,7 +88,7 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder == null) { return Response.notFound(); } diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index a8ed1b0f281..f486acbe991 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -118,7 +118,7 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder == null) { return Response.notFound(); } diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index d2ca9edbd48..594dc5c183d 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -353,7 +353,7 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; if (Utils.isGuid(id)) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder != null) { decFolder = await folder.decrypt(); } diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 85d4a1ab6a6..b9c437f591d 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -30,6 +30,6 @@ }, "_moduleAliases": { "@bitwarden/common": "dist/libs/common/src", - "@bitwarden/node/services/nodeCryptoFunction.service": "dist/libs/node/src/services/nodeCryptoFunction.service" + "@bitwarden/node/services/node-crypto-function.service": "dist/libs/node/src/services/node-crypto-function.service" } } diff --git a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts index 916ec3c286e..d1f29493fc0 100644 --- a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts +++ b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts @@ -7,7 +7,7 @@ import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData"; import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage"; diff --git a/libs/angular/src/components/two-factor.component.ts b/libs/angular/src/components/two-factor.component.ts index f61e54710b2..28799df6600 100644 --- a/libs/angular/src/components/two-factor.component.ts +++ b/libs/angular/src/components/two-factor.component.ts @@ -281,12 +281,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI return ( this.authService.authingWithPassword() || this.authService.authingWithSso() || - this.authService.authingWithApiKey() || + this.authService.authingWithUserApiKey() || this.authService.authingWithPasswordless() ); } get needsLock(): boolean { - return this.authService.authingWithSso() || this.authService.authingWithApiKey(); + return this.authService.authingWithSso() || this.authService.authingWithUserApiKey(); } } diff --git a/libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts similarity index 90% rename from libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts rename to libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts index 6e80ffa2916..bc61d2c7e22 100644 --- a/libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts @@ -12,13 +12,13 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { ApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/apiLogin.strategy"; +import { UserApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/user-api-login.strategy"; import { Utils } from "@bitwarden/common/misc/utils"; -import { ApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials"; +import { UserApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials"; import { identityTokenResponseFactory } from "./logIn.strategy.spec"; -describe("ApiLogInStrategy", () => { +describe("UserApiLogInStrategy", () => { let cryptoService: SubstituteOf; let apiService: SubstituteOf; let tokenService: SubstituteOf; @@ -31,8 +31,8 @@ describe("ApiLogInStrategy", () => { let stateService: SubstituteOf; let twoFactorService: SubstituteOf; - let apiLogInStrategy: ApiLogInStrategy; - let credentials: ApiLogInCredentials; + let apiLogInStrategy: UserApiLogInStrategy; + let credentials: UserApiLogInCredentials; const deviceId = Utils.newGuid(); const keyConnectorUrl = "KEY_CONNECTOR_URL"; @@ -55,7 +55,7 @@ describe("ApiLogInStrategy", () => { appIdService.getAppId().resolves(deviceId); tokenService.getTwoFactorToken().resolves(null); - apiLogInStrategy = new ApiLogInStrategy( + apiLogInStrategy = new UserApiLogInStrategy( cryptoService, apiService, tokenService, @@ -69,7 +69,7 @@ describe("ApiLogInStrategy", () => { keyConnectorService ); - credentials = new ApiLogInCredentials(apiClientId, apiClientSecret); + credentials = new UserApiLogInCredentials(apiClientId, apiClientSecret); }); it("sends api key credentials to the server", async () => { diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 74f9edebd6f..a8e2f45283c 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -24,9 +24,9 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access import { EventRequest } from "../models/request/event.request"; import { GroupRequest } from "../models/request/group.request"; import { IapCheckRequest } from "../models/request/iap-check.request"; -import { ApiTokenRequest } from "../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; +import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request"; import { ImportCiphersRequest } from "../models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -175,7 +175,7 @@ export abstract class ApiService { ) => Promise; postIdentityToken: ( - request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest + request: PasswordTokenRequest | SsoTokenRequest | UserApiTokenRequest ) => Promise; refreshIdentityToken: () => Promise; diff --git a/libs/common/src/abstractions/auth.service.ts b/libs/common/src/abstractions/auth.service.ts index cadc7bb60b3..ca75e273b5c 100644 --- a/libs/common/src/abstractions/auth.service.ts +++ b/libs/common/src/abstractions/auth.service.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { AuthenticationStatus } from "../enums/authenticationStatus"; import { AuthResult } from "../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, @@ -20,7 +20,7 @@ export abstract class AuthService { logIn: ( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials @@ -31,7 +31,7 @@ export abstract class AuthService { ) => Promise; logOut: (callback: () => void) => void; makePreloginKey: (masterPassword: string, email: string) => Promise; - authingWithApiKey: () => boolean; + authingWithUserApiKey: () => boolean; authingWithSso: () => boolean; authingWithPassword: () => boolean; authingWithPasswordless: () => boolean; diff --git a/libs/common/src/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/abstractions/folder/folder.service.abstraction.ts index f8df1f7db63..44db4a985b4 100644 --- a/libs/common/src/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/abstractions/folder/folder.service.abstraction.ts @@ -12,6 +12,10 @@ export abstract class FolderService { clearCache: () => Promise; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; get: (id: string) => Promise; + /** + * @deprecated Only use in CLI! + */ + getFromState: (id: string) => Promise; /** * @deprecated Only use in CLI! */ diff --git a/libs/common/src/enums/authenticationType.ts b/libs/common/src/enums/authenticationType.ts index 5133c4f648e..531c571b469 100644 --- a/libs/common/src/enums/authenticationType.ts +++ b/libs/common/src/enums/authenticationType.ts @@ -1,6 +1,6 @@ export enum AuthenticationType { Password = 0, Sso = 1, - Api = 2, + UserApi = 2, Passwordless = 3, } diff --git a/libs/common/src/misc/logInStrategies/logIn.strategy.ts b/libs/common/src/misc/logInStrategies/logIn.strategy.ts index 8ac3ea2b55b..b0cf06e9f9a 100644 --- a/libs/common/src/misc/logInStrategies/logIn.strategy.ts +++ b/libs/common/src/misc/logInStrategies/logIn.strategy.ts @@ -11,23 +11,23 @@ import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; import { Account, AccountProfile, AccountTokens } from "../../models/domain/account"; import { AuthResult } from "../../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, } from "../../models/domain/log-in-credentials"; import { DeviceRequest } from "../../models/request/device.request"; -import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../../models/request/identity-token/sso-token.request"; import { TokenTwoFactorRequest } from "../../models/request/identity-token/token-two-factor.request"; +import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request"; import { KeysRequest } from "../../models/request/keys.request"; import { IdentityCaptchaResponse } from "../../models/response/identity-captcha.response"; import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; export abstract class LogInStrategy { - protected abstract tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; + protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; protected captchaBypassToken: string = null; constructor( @@ -44,7 +44,7 @@ export abstract class LogInStrategy { abstract logIn( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials diff --git a/libs/common/src/misc/logInStrategies/apiLogin.strategy.ts b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts similarity index 85% rename from libs/common/src/misc/logInStrategies/apiLogin.strategy.ts rename to libs/common/src/misc/logInStrategies/user-api-login.strategy.ts index a60735343ad..7b435c74fd4 100644 --- a/libs/common/src/misc/logInStrategies/apiLogin.strategy.ts +++ b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts @@ -9,14 +9,14 @@ import { PlatformUtilsService } from "../../abstractions/platformUtils.service"; import { StateService } from "../../abstractions/state.service"; import { TokenService } from "../../abstractions/token.service"; import { TwoFactorService } from "../../abstractions/twoFactor.service"; -import { ApiLogInCredentials } from "../../models/domain/log-in-credentials"; -import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request"; +import { UserApiLogInCredentials } from "../../models/domain/log-in-credentials"; +import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request"; import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { LogInStrategy } from "./logIn.strategy"; -export class ApiLogInStrategy extends LogInStrategy { - tokenRequest: ApiTokenRequest; +export class UserApiLogInStrategy extends LogInStrategy { + tokenRequest: UserApiTokenRequest; constructor( cryptoService: CryptoService, @@ -51,8 +51,8 @@ export class ApiLogInStrategy extends LogInStrategy { } } - async logIn(credentials: ApiLogInCredentials) { - this.tokenRequest = new ApiTokenRequest( + async logIn(credentials: UserApiLogInCredentials) { + this.tokenRequest = new UserApiTokenRequest( credentials.clientId, credentials.clientSecret, await this.buildTwoFactor(), diff --git a/libs/common/src/models/domain/log-in-credentials.ts b/libs/common/src/models/domain/log-in-credentials.ts index 46f2fe5f82e..81323934bf0 100644 --- a/libs/common/src/models/domain/log-in-credentials.ts +++ b/libs/common/src/models/domain/log-in-credentials.ts @@ -26,8 +26,8 @@ export class SsoLogInCredentials { ) {} } -export class ApiLogInCredentials { - readonly type = AuthenticationType.Api; +export class UserApiLogInCredentials { + readonly type = AuthenticationType.UserApi; constructor(public clientId: string, public clientSecret: string) {} } diff --git a/libs/common/src/models/request/identity-token/token.request.ts b/libs/common/src/models/request/identity-token/token.request.ts index 91007ce3424..32becd222a9 100644 --- a/libs/common/src/models/request/identity-token/token.request.ts +++ b/libs/common/src/models/request/identity-token/token.request.ts @@ -42,10 +42,12 @@ export abstract class TokenRequest { obj.authRequest = this.passwordlessAuthRequest; } - 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"; + 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/common/src/models/request/identity-token/api-token.request.ts b/libs/common/src/models/request/identity-token/user-api-token.request.ts similarity index 91% rename from libs/common/src/models/request/identity-token/api-token.request.ts rename to libs/common/src/models/request/identity-token/user-api-token.request.ts index 60c8b6d3817..15a9bbfe681 100644 --- a/libs/common/src/models/request/identity-token/api-token.request.ts +++ b/libs/common/src/models/request/identity-token/user-api-token.request.ts @@ -3,7 +3,7 @@ import { DeviceRequest } from "../device.request"; import { TokenTwoFactorRequest } from "./token-two-factor.request"; import { TokenRequest } from "./token.request"; -export class ApiTokenRequest extends TokenRequest { +export class UserApiTokenRequest extends TokenRequest { constructor( public clientId: string, public clientSecret: string, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 6c15d85c0d1..6b660bb367e 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -31,10 +31,10 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access import { EventRequest } from "../models/request/event.request"; import { GroupRequest } from "../models/request/group.request"; import { IapCheckRequest } from "../models/request/iap-check.request"; -import { ApiTokenRequest } from "../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request"; +import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request"; import { ImportCiphersRequest } from "../models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -206,7 +206,7 @@ export class ApiService implements ApiServiceAbstraction { // Auth APIs async postIdentityToken( - request: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest + request: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest ): Promise { const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", @@ -219,7 +219,7 @@ export class ApiService implements ApiServiceAbstraction { request.alterIdentityTokenHeaders(headers); const identityToken = - request instanceof ApiTokenRequest + request instanceof UserApiTokenRequest ? request.toIdentityToken() : request.toIdentityToken(this.platformUtilsService.getClientType()); @@ -2271,8 +2271,7 @@ export class ApiService implements ApiServiceAbstraction { const appId = await this.appIdService.getAppId(); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); - - const tokenRequest = new ApiTokenRequest( + const tokenRequest = new UserApiTokenRequest( clientId, clientSecret, new TokenTwoFactorRequest(), diff --git a/libs/common/src/services/auth.service.ts b/libs/common/src/services/auth.service.ts index 4a851fb13df..4e97f701113 100644 --- a/libs/common/src/services/auth.service.ts +++ b/libs/common/src/services/auth.service.ts @@ -17,13 +17,13 @@ import { AuthenticationStatus } from "../enums/authenticationStatus"; import { AuthenticationType } from "../enums/authenticationType"; import { KdfType } from "../enums/kdfType"; import { KeySuffixOptions } from "../enums/keySuffixOptions"; -import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy"; import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy"; import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy"; import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy"; +import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy"; import { AuthResult } from "../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, @@ -67,7 +67,7 @@ export class AuthService implements AuthServiceAbstraction { } private logInStrategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy; @@ -92,7 +92,7 @@ export class AuthService implements AuthServiceAbstraction { async logIn( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials @@ -100,7 +100,7 @@ export class AuthService implements AuthServiceAbstraction { this.clearState(); let strategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy; @@ -134,8 +134,8 @@ export class AuthService implements AuthServiceAbstraction { this.keyConnectorService ); break; - case AuthenticationType.Api: - strategy = new ApiLogInStrategy( + case AuthenticationType.UserApi: + strategy = new UserApiLogInStrategy( this.cryptoService, this.apiService, this.tokenService, @@ -203,8 +203,8 @@ export class AuthService implements AuthServiceAbstraction { this.messagingService.send("loggedOut"); } - authingWithApiKey(): boolean { - return this.logInStrategy instanceof ApiLogInStrategy; + authingWithUserApiKey(): boolean { + return this.logInStrategy instanceof UserApiLogInStrategy; } authingWithSso(): boolean { @@ -272,7 +272,7 @@ export class AuthService implements AuthServiceAbstraction { private saveState( strategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy diff --git a/libs/common/src/services/folder/folder.service.ts b/libs/common/src/services/folder/folder.service.ts index 4d8981c4eae..096d89cea9f 100644 --- a/libs/common/src/services/folder/folder.service.ts +++ b/libs/common/src/services/folder/folder.service.ts @@ -64,6 +64,20 @@ export class FolderService implements InternalFolderServiceAbstraction { return folders.find((folder) => folder.id === id); } + /** + * @deprecated For the CLI only + * @param id id of the folder + */ + async getFromState(id: string): Promise { + const foldersMap = await this.stateService.getEncryptedFolders(); + const folder = foldersMap[id]; + if (folder == null) { + return null; + } + + return new Folder(folder); + } + /** * @deprecated Only use in CLI! */ diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts new file mode 100644 index 00000000000..b55384dea1f --- /dev/null +++ b/libs/components/src/color-password/color-password.component.ts @@ -0,0 +1,79 @@ +import { Component, HostBinding, Input } from "@angular/core"; + +import { Utils } from "@bitwarden/common/misc/utils"; + +enum CharacterType { + Letter, + Emoji, + Special, + Number, +} + +@Component({ + selector: "bit-color-password", + template: `
+ {{ character }} + {{ + i + 1 + }} +
`, +}) +export class ColorPasswordComponent { + @Input() private password: string = null; + @Input() showCount = false; + + characterStyles: Record = { + [CharacterType.Emoji]: [], + [CharacterType.Letter]: ["tw-text-main"], + [CharacterType.Special]: ["tw-text-danger"], + [CharacterType.Number]: ["tw-text-primary-500"], + }; + + @HostBinding("class") + get classList() { + return ["tw-min-w-0", "tw-whitespace-pre-wrap", "tw-break-all"]; + } + + get passwordArray() { + // Convert to an array to handle cases that strings have special characters, i.e.: emoji. + return Array.from(this.password); + } + + getCharacterClass(character: string) { + const charType = this.getCharacterType(character); + const charClass = this.characterStyles[charType].concat("tw-inline-flex"); + + if (this.showCount) { + return charClass.concat([ + "tw-inline-flex", + "tw-flex-col", + "tw-items-center", + "tw-w-7", + "tw-py-1", + "odd:tw-bg-secondary-100", + ]); + } + + return charClass; + } + + private getCharacterType(character: string): CharacterType { + if (character.match(Utils.regexpEmojiPresentation)) { + return CharacterType.Emoji; + } + + if (character.match(/\d/)) { + return CharacterType.Number; + } + + const specials = ["&", "<", ">", " "]; + if (specials.includes(character) || character.match(/[^\w ]/)) { + return CharacterType.Special; + } + + return CharacterType.Letter; + } +} diff --git a/libs/components/src/color-password/color-password.module.ts b/libs/components/src/color-password/color-password.module.ts new file mode 100644 index 00000000000..692c206bb4c --- /dev/null +++ b/libs/components/src/color-password/color-password.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { ColorPasswordComponent } from "./color-password.component"; + +@NgModule({ + imports: [CommonModule], + exports: [ColorPasswordComponent], + declarations: [ColorPasswordComponent], +}) +export class ColorPasswordModule {} diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts new file mode 100644 index 00000000000..87565b81273 --- /dev/null +++ b/libs/components/src/color-password/color-password.stories.ts @@ -0,0 +1,52 @@ +import { Meta, Story } from "@storybook/angular"; + +import { ColorPasswordComponent } from "./color-password.component"; + +const examplePassword = "Wq$Jk😀7jDX#rS5Sdi!z"; + +export default { + title: "Component Library/Color Password", + component: ColorPasswordComponent, + args: { + password: examplePassword, + showCount: false, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/6fvTDa3zfvgWdizLQ7nSTP/Numbered-Password", + }, + }, +} as Meta; + +const Template: Story = (args: ColorPasswordComponent) => ({ + props: args, + template: ` + + `, +}); + +const WrappedTemplate: Story = (args: ColorPasswordComponent) => ({ + props: args, + template: ` +
+ +
+ `, +}); + +export const ColorPassword = Template.bind({}); + +export const WrappedColorPassword = WrappedTemplate.bind({}); + +export const ColorPasswordCount = Template.bind({}); +ColorPasswordCount.args = { + password: examplePassword, + showCount: true, +}; + +export const WrappedColorPasswordCount = WrappedTemplate.bind({}); +WrappedColorPasswordCount.args = { + password: examplePassword, + showCount: true, +}; diff --git a/libs/components/src/color-password/index.ts b/libs/components/src/color-password/index.ts new file mode 100644 index 00000000000..86718f037f7 --- /dev/null +++ b/libs/components/src/color-password/index.ts @@ -0,0 +1 @@ +export * from "./color-password.module"; diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 942891ba061..678494cd9f8 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -1,10 +1,12 @@ -import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { ButtonModule } from "../button"; +import { IconButtonModule } from "../icon-button"; +import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; import { DialogService } from "./dialog.service"; @@ -35,7 +37,7 @@ class StoryDialogComponent { @Component({ selector: "story-dialog-content", template: ` - + Dialog Title Dialog body text goes here. @@ -68,7 +70,7 @@ export default { DialogTitleContainerDirective, StoryDialogContentComponent, ], - imports: [ButtonModule, DialogModule], + imports: [SharedModule, ButtonModule, DialogModule, IconButtonModule], providers: [ DialogService, { diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index e217c6bf026..8abb6bade3d 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -14,4 +14,5 @@ export * from "./multi-select"; export * from "./tabs"; export * from "./table"; export * from "./toggle-group"; +export * from "./color-password"; export * from "./utils/i18n-mock.service"; diff --git a/libs/components/src/tabs/tab-group/tab-group.component.html b/libs/components/src/tabs/tab-group/tab-group.component.html index dbe71fb4b99..071f5c2259f 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.html +++ b/libs/components/src/tabs/tab-group/tab-group.component.html @@ -34,7 +34,7 @@ role="tabpanel" *ngFor="let tab of tabs; let i = index" [id]="getTabContentId(i)" - [attr.tabindex]="selectedIndex === i ? 0 : -1" + [attr.tabindex]="tab.contentTabIndex" [attr.labeledby]="getTabLabelId(i)" [active]="tab.isActive" [content]="tab.content" diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 05e61ffacc2..9a3a9380031 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -20,9 +20,18 @@ import { TabLabelDirective } from "./tab-label.directive"; }) export class TabComponent implements OnInit { @Input() disabled = false; - @Input("label") textLabel = ""; + /** + * Optional tabIndex for the tabPanel that contains this tab's content. + * + * If the tabpanel does not contain any focusable elements or the first element with content is not focusable, + * this should be set to 0 to include it in the tab sequence of the page. + * + * @remarks See note 4 of https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ + */ + @Input() contentTabIndex: number | undefined; + @ViewChild(TemplateRef, { static: true }) implicitContent: TemplateRef; @ContentChild(TabLabelDirective) templateLabel: TabLabelDirective; diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index a520b50e17a..32c7e950e73 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -3,6 +3,9 @@ import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; +import { ButtonModule } from "../button"; +import { FormFieldModule } from "../form-field"; + import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabsModule } from "./tabs.module"; @@ -44,6 +47,8 @@ export default { imports: [ CommonModule, TabsModule, + ButtonModule, + FormFieldModule, RouterModule.forRoot( [ { path: "", redirectTo: "active", pathMatch: "full" }, @@ -125,3 +130,32 @@ const PreserveContentTabGroupTemplate: Story = (args: any) => }); export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({}); + +const KeyboardNavTabGroupTemplate: Story = (args: any) => ({ + props: args, + template: ` + + +

+ You can navigate through all tab labels, form inputs, and the button that is outside the tab group via + the keyboard. +

+ + First Input + + + + Second Input + + +
+ + +

This tab has no focusable content, but the panel should still be focusable

+
+
+ +`, +}); + +export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({}); diff --git a/libs/node/spec/services/nodeCryptoFunction.service.spec.ts b/libs/node/spec/services/node-crypto-function.service.spec.ts similarity index 99% rename from libs/node/spec/services/nodeCryptoFunction.service.spec.ts rename to libs/node/spec/services/node-crypto-function.service.spec.ts index d0097c29304..1dbdcfb93ae 100644 --- a/libs/node/spec/services/nodeCryptoFunction.service.spec.ts +++ b/libs/node/spec/services/node-crypto-function.service.spec.ts @@ -1,6 +1,6 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; const RsaPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + diff --git a/libs/node/src/cli/commands/login.command.ts b/libs/node/src/cli/commands/login.command.ts index dfff7b9ad20..413b5add5ee 100644 --- a/libs/node/src/cli/commands/login.command.ts +++ b/libs/node/src/cli/commands/login.command.ts @@ -21,7 +21,7 @@ import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { Utils } from "@bitwarden/common/misc/utils"; import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, } from "@bitwarden/common/models/domain/log-in-credentials"; @@ -160,7 +160,12 @@ export class LoginCommand { let response: AuthResult = null; if (clientId != null && clientSecret != null) { - response = await this.authService.logIn(new ApiLogInCredentials(clientId, clientSecret)); + if (!clientId.startsWith("user")) { + return Response.error("Invalid API Key; Organization API Key currently not supported"); + } + response = await this.authService.logIn( + new UserApiLogInCredentials(clientId, clientSecret) + ); } else if (ssoCode != null && ssoCodeVerifier != null) { response = await this.authService.logIn( new SsoLogInCredentials( diff --git a/libs/node/src/services/nodeCryptoFunction.service.ts b/libs/node/src/services/node-crypto-function.service.ts similarity index 100% rename from libs/node/src/services/nodeCryptoFunction.service.ts rename to libs/node/src/services/node-crypto-function.service.ts