1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Shane Melton
2022-11-17 15:02:50 -08:00
34 changed files with 353 additions and 86 deletions

View File

@@ -36,7 +36,6 @@
./libs/angular/src/interfaces/selectOptions.ts ./libs/angular/src/interfaces/selectOptions.ts
./libs/components/src/stories/Introduction.stories.mdx ./libs/components/src/stories/Introduction.stories.mdx
./libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts ./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/passwordLogIn.strategy.spec.ts
./libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts ./libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts
./libs/common/spec/web/services/webCryptoFunction.service.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/spec/services/consoleLog.service.spec.ts
./libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts ./libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts
./libs/common/src/misc/logInStrategies/passwordLogin.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/passwordlessLogin.strategy.ts
./libs/common/src/misc/logInStrategies/logIn.strategy.ts ./libs/common/src/misc/logInStrategies/logIn.strategy.ts
./libs/common/src/misc/nodeUtils.ts ./libs/common/src/misc/nodeUtils.ts
@@ -165,7 +163,6 @@
./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/services/webCryptoFunction.service.ts
./libs/common/src/interfaces/IEncrypted.ts ./libs/common/src/interfaces/IEncrypted.ts
./libs/node/spec/cli/consoleLog.service.spec.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/baseResponse.ts
./libs/node/src/cli/models/response/stringResponse.ts ./libs/node/src/cli/models/response/stringResponse.ts
./libs/node/src/cli/models/response/fileResponse.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/consoleLog.service.ts
./libs/node/src/cli/services/cliPlatformUtils.service.ts ./libs/node/src/cli/services/cliPlatformUtils.service.ts
./libs/node/src/services/nodeApi.service.ts ./libs/node/src/services/nodeApi.service.ts
./libs/node/src/services/nodeCryptoFunction.service.ts
./libs/node/src/services/lowdbStorage.service.ts ./libs/node/src/services/lowdbStorage.service.ts
./libs/electron/spec/services/electronLog.service.spec.ts ./libs/electron/spec/services/electronLog.service.spec.ts
./libs/electron/src/baseMenu.ts ./libs/electron/src/baseMenu.ts

View File

@@ -6,6 +6,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { AngularWebpackPlugin } = require("@ngtools/webpack"); const { AngularWebpackPlugin } = require("@ngtools/webpack");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin");
const configurator = require("./config/config"); const configurator = require("./config/config");
if (process.env.NODE_ENV == null) { 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 = [ const plugins = [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: "./src/popup/index.html", template: "./src/popup/index.html",
filename: "popup/index.html", filename: "popup/index.html",
chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"],
}), }),
new HtmlWebpackPlugin({
template: "./src/background.html",
filename: "background.html",
chunks: ["vendor", "background"],
}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: "./src/notification/bar.html", template: "./src/notification/bar.html",
filename: "notification/bar.html", filename: "notification/bar.html",
@@ -99,11 +107,6 @@ const plugins = [
filename: "[name].css", filename: "[name].css",
chunkFilename: "chunk-[id].css", chunkFilename: "chunk-[id].css",
}), }),
new webpack.DefinePlugin({
"process.env": {
ENV: JSON.stringify(ENV),
},
}),
new AngularWebpackPlugin({ new AngularWebpackPlugin({
tsConfigPath: "tsconfig.json", tsConfigPath: "tsconfig.json",
entryModule: "src/popup/app.module#AppModule", entryModule: "src/popup/app.module#AppModule",
@@ -119,19 +122,20 @@ const plugins = [
exclude: [/content\/.*/, /notification\/.*/], exclude: [/content\/.*/, /notification\/.*/],
filename: "[file].map", filename: "[file].map",
}), }),
new webpack.EnvironmentPlugin({ ...requiredPlugins,
FLAGS: envConfig.flags,
DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {},
}),
]; ];
const config = { /**
* @type {import("webpack").Configuration}
* This config compiles everything but the background
*/
const mainConfig = {
name: "main",
mode: ENV, mode: ENV,
devtool: false, devtool: false,
entry: { entry: {
"popup/polyfills": "./src/popup/polyfills.ts", "popup/polyfills": "./src/popup/polyfills.ts",
"popup/main": "./src/popup/main.ts", "popup/main": "./src/popup/main.ts",
background: "./src/background.ts",
"content/autofill": "./src/content/autofill.js", "content/autofill": "./src/content/autofill.js",
"content/autofiller": "./src/content/autofiller.ts", "content/autofiller": "./src/content/autofiller.ts",
"content/notificationBar": "./src/content/notificationBar.ts", "content/notificationBar": "./src/content/notificationBar.ts",
@@ -209,18 +213,72 @@ const config = {
plugins: plugins, plugins: plugins,
}; };
/**
* @type {import("webpack").Configuration[]}
*/
const configs = [];
if (manifestVersion == 2) { if (manifestVersion == 2) {
// We can't use this in manifest v3 mainConfig.optimization.splitChunks.cacheGroups.commons2 = {
// Ideally we understand why this breaks it and we don't have to do this
config.optimization.splitChunks.cacheGroups.commons2 = {
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
name: "vendor", name: "vendor",
chunks: (chunk) => { chunks: (chunk) => {
return chunk.name === "background"; 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 { } 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;

View File

@@ -51,8 +51,8 @@ import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vau
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.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 { NodeApiService } from "@bitwarden/node/services/nodeApi.service";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service";
import { Program } from "./program"; import { Program } from "./program";
import { SendProgram } from "./send.program"; import { SendProgram } from "./send.program";

View File

@@ -88,7 +88,7 @@ export class DeleteCommand {
} }
private async deleteFolder(id: string) { private async deleteFolder(id: string) {
const folder = await this.folderService.get(id); const folder = await this.folderService.getFromState(id);
if (folder == null) { if (folder == null) {
return Response.notFound(); return Response.notFound();
} }

View File

@@ -118,7 +118,7 @@ export class EditCommand {
} }
private async editFolder(id: string, req: FolderExport) { private async editFolder(id: string, req: FolderExport) {
const folder = await this.folderService.get(id); const folder = await this.folderService.getFromState(id);
if (folder == null) { if (folder == null) {
return Response.notFound(); return Response.notFound();
} }

View File

@@ -353,7 +353,7 @@ export class GetCommand extends DownloadCommand {
private async getFolder(id: string) { private async getFolder(id: string) {
let decFolder: FolderView = null; let decFolder: FolderView = null;
if (Utils.isGuid(id)) { if (Utils.isGuid(id)) {
const folder = await this.folderService.get(id); const folder = await this.folderService.getFromState(id);
if (folder != null) { if (folder != null) {
decFolder = await folder.decrypt(); decFolder = await folder.decrypt();
} }

View File

@@ -30,6 +30,6 @@
}, },
"_moduleAliases": { "_moduleAliases": {
"@bitwarden/common": "dist/libs/common/src", "@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"
} }
} }

View File

@@ -7,7 +7,7 @@ import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; 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 { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData";
import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage"; import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage";

View File

@@ -281,12 +281,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
return ( return (
this.authService.authingWithPassword() || this.authService.authingWithPassword() ||
this.authService.authingWithSso() || this.authService.authingWithSso() ||
this.authService.authingWithApiKey() || this.authService.authingWithUserApiKey() ||
this.authService.authingWithPasswordless() this.authService.authingWithPasswordless()
); );
} }
get needsLock(): boolean { get needsLock(): boolean {
return this.authService.authingWithSso() || this.authService.authingWithApiKey(); return this.authService.authingWithSso() || this.authService.authingWithUserApiKey();
} }
} }

View File

@@ -12,13 +12,13 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.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 { 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"; import { identityTokenResponseFactory } from "./logIn.strategy.spec";
describe("ApiLogInStrategy", () => { describe("UserApiLogInStrategy", () => {
let cryptoService: SubstituteOf<CryptoService>; let cryptoService: SubstituteOf<CryptoService>;
let apiService: SubstituteOf<ApiService>; let apiService: SubstituteOf<ApiService>;
let tokenService: SubstituteOf<TokenService>; let tokenService: SubstituteOf<TokenService>;
@@ -31,8 +31,8 @@ describe("ApiLogInStrategy", () => {
let stateService: SubstituteOf<StateService>; let stateService: SubstituteOf<StateService>;
let twoFactorService: SubstituteOf<TwoFactorService>; let twoFactorService: SubstituteOf<TwoFactorService>;
let apiLogInStrategy: ApiLogInStrategy; let apiLogInStrategy: UserApiLogInStrategy;
let credentials: ApiLogInCredentials; let credentials: UserApiLogInCredentials;
const deviceId = Utils.newGuid(); const deviceId = Utils.newGuid();
const keyConnectorUrl = "KEY_CONNECTOR_URL"; const keyConnectorUrl = "KEY_CONNECTOR_URL";
@@ -55,7 +55,7 @@ describe("ApiLogInStrategy", () => {
appIdService.getAppId().resolves(deviceId); appIdService.getAppId().resolves(deviceId);
tokenService.getTwoFactorToken().resolves(null); tokenService.getTwoFactorToken().resolves(null);
apiLogInStrategy = new ApiLogInStrategy( apiLogInStrategy = new UserApiLogInStrategy(
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@@ -69,7 +69,7 @@ describe("ApiLogInStrategy", () => {
keyConnectorService keyConnectorService
); );
credentials = new ApiLogInCredentials(apiClientId, apiClientSecret); credentials = new UserApiLogInCredentials(apiClientId, apiClientSecret);
}); });
it("sends api key credentials to the server", async () => { it("sends api key credentials to the server", async () => {

View File

@@ -24,9 +24,9 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access
import { EventRequest } from "../models/request/event.request"; import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request"; import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.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 { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-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 { ImportCiphersRequest } from "../models/request/import-ciphers.request";
import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request";
import { KdfRequest } from "../models/request/kdf.request"; import { KdfRequest } from "../models/request/kdf.request";
@@ -175,7 +175,7 @@ export abstract class ApiService {
) => Promise<any>; ) => Promise<any>;
postIdentityToken: ( postIdentityToken: (
request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest request: PasswordTokenRequest | SsoTokenRequest | UserApiTokenRequest
) => Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>; ) => Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>;
refreshIdentityToken: () => Promise<any>; refreshIdentityToken: () => Promise<any>;

View File

@@ -3,7 +3,7 @@ import { Observable } from "rxjs";
import { AuthenticationStatus } from "../enums/authenticationStatus"; import { AuthenticationStatus } from "../enums/authenticationStatus";
import { AuthResult } from "../models/domain/auth-result"; import { AuthResult } from "../models/domain/auth-result";
import { import {
ApiLogInCredentials, UserApiLogInCredentials,
PasswordLogInCredentials, PasswordLogInCredentials,
SsoLogInCredentials, SsoLogInCredentials,
PasswordlessLogInCredentials, PasswordlessLogInCredentials,
@@ -20,7 +20,7 @@ export abstract class AuthService {
logIn: ( logIn: (
credentials: credentials:
| ApiLogInCredentials | UserApiLogInCredentials
| PasswordLogInCredentials | PasswordLogInCredentials
| SsoLogInCredentials | SsoLogInCredentials
| PasswordlessLogInCredentials | PasswordlessLogInCredentials
@@ -31,7 +31,7 @@ export abstract class AuthService {
) => Promise<AuthResult>; ) => Promise<AuthResult>;
logOut: (callback: () => void) => void; logOut: (callback: () => void) => void;
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>; makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
authingWithApiKey: () => boolean; authingWithUserApiKey: () => boolean;
authingWithSso: () => boolean; authingWithSso: () => boolean;
authingWithPassword: () => boolean; authingWithPassword: () => boolean;
authingWithPasswordless: () => boolean; authingWithPasswordless: () => boolean;

View File

@@ -12,6 +12,10 @@ export abstract class FolderService {
clearCache: () => Promise<void>; clearCache: () => Promise<void>;
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
get: (id: string) => Promise<Folder>; get: (id: string) => Promise<Folder>;
/**
* @deprecated Only use in CLI!
*/
getFromState: (id: string) => Promise<Folder>;
/** /**
* @deprecated Only use in CLI! * @deprecated Only use in CLI!
*/ */

View File

@@ -1,6 +1,6 @@
export enum AuthenticationType { export enum AuthenticationType {
Password = 0, Password = 0,
Sso = 1, Sso = 1,
Api = 2, UserApi = 2,
Passwordless = 3, Passwordless = 3,
} }

View File

@@ -11,23 +11,23 @@ import { TwoFactorProviderType } from "../../enums/twoFactorProviderType";
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account"; import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
import { AuthResult } from "../../models/domain/auth-result"; import { AuthResult } from "../../models/domain/auth-result";
import { import {
ApiLogInCredentials, UserApiLogInCredentials,
PasswordLogInCredentials, PasswordLogInCredentials,
SsoLogInCredentials, SsoLogInCredentials,
PasswordlessLogInCredentials, PasswordlessLogInCredentials,
} from "../../models/domain/log-in-credentials"; } from "../../models/domain/log-in-credentials";
import { DeviceRequest } from "../../models/request/device.request"; 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 { PasswordTokenRequest } from "../../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../../models/request/identity-token/sso-token.request"; import { SsoTokenRequest } from "../../models/request/identity-token/sso-token.request";
import { TokenTwoFactorRequest } from "../../models/request/identity-token/token-two-factor.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 { KeysRequest } from "../../models/request/keys.request";
import { IdentityCaptchaResponse } from "../../models/response/identity-captcha.response"; import { IdentityCaptchaResponse } from "../../models/response/identity-captcha.response";
import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { IdentityTokenResponse } from "../../models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response";
export abstract class LogInStrategy { export abstract class LogInStrategy {
protected abstract tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
protected captchaBypassToken: string = null; protected captchaBypassToken: string = null;
constructor( constructor(
@@ -44,7 +44,7 @@ export abstract class LogInStrategy {
abstract logIn( abstract logIn(
credentials: credentials:
| ApiLogInCredentials | UserApiLogInCredentials
| PasswordLogInCredentials | PasswordLogInCredentials
| SsoLogInCredentials | SsoLogInCredentials
| PasswordlessLogInCredentials | PasswordlessLogInCredentials

View File

@@ -9,14 +9,14 @@ import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
import { StateService } from "../../abstractions/state.service"; import { StateService } from "../../abstractions/state.service";
import { TokenService } from "../../abstractions/token.service"; import { TokenService } from "../../abstractions/token.service";
import { TwoFactorService } from "../../abstractions/twoFactor.service"; import { TwoFactorService } from "../../abstractions/twoFactor.service";
import { ApiLogInCredentials } from "../../models/domain/log-in-credentials"; import { UserApiLogInCredentials } from "../../models/domain/log-in-credentials";
import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request"; import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request";
import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { IdentityTokenResponse } from "../../models/response/identity-token.response";
import { LogInStrategy } from "./logIn.strategy"; import { LogInStrategy } from "./logIn.strategy";
export class ApiLogInStrategy extends LogInStrategy { export class UserApiLogInStrategy extends LogInStrategy {
tokenRequest: ApiTokenRequest; tokenRequest: UserApiTokenRequest;
constructor( constructor(
cryptoService: CryptoService, cryptoService: CryptoService,
@@ -51,8 +51,8 @@ export class ApiLogInStrategy extends LogInStrategy {
} }
} }
async logIn(credentials: ApiLogInCredentials) { async logIn(credentials: UserApiLogInCredentials) {
this.tokenRequest = new ApiTokenRequest( this.tokenRequest = new UserApiTokenRequest(
credentials.clientId, credentials.clientId,
credentials.clientSecret, credentials.clientSecret,
await this.buildTwoFactor(), await this.buildTwoFactor(),

View File

@@ -26,8 +26,8 @@ export class SsoLogInCredentials {
) {} ) {}
} }
export class ApiLogInCredentials { export class UserApiLogInCredentials {
readonly type = AuthenticationType.Api; readonly type = AuthenticationType.UserApi;
constructor(public clientId: string, public clientSecret: string) {} constructor(public clientId: string, public clientSecret: string) {}
} }

View File

@@ -42,10 +42,12 @@ export abstract class TokenRequest {
obj.authRequest = this.passwordlessAuthRequest; obj.authRequest = this.passwordlessAuthRequest;
} }
if (this.twoFactor.token && this.twoFactor.provider != null) { if (this.twoFactor) {
obj.twoFactorToken = this.twoFactor.token; if (this.twoFactor.token && this.twoFactor.provider != null) {
obj.twoFactorProvider = this.twoFactor.provider; obj.twoFactorToken = this.twoFactor.token;
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0"; obj.twoFactorProvider = this.twoFactor.provider;
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0";
}
} }
return obj; return obj;

View File

@@ -3,7 +3,7 @@ import { DeviceRequest } from "../device.request";
import { TokenTwoFactorRequest } from "./token-two-factor.request"; import { TokenTwoFactorRequest } from "./token-two-factor.request";
import { TokenRequest } from "./token.request"; import { TokenRequest } from "./token.request";
export class ApiTokenRequest extends TokenRequest { export class UserApiTokenRequest extends TokenRequest {
constructor( constructor(
public clientId: string, public clientId: string,
public clientSecret: string, public clientSecret: string,

View File

@@ -31,10 +31,10 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access
import { EventRequest } from "../models/request/event.request"; import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request"; import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.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 { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.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 { ImportCiphersRequest } from "../models/request/import-ciphers.request";
import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request";
import { KdfRequest } from "../models/request/kdf.request"; import { KdfRequest } from "../models/request/kdf.request";
@@ -206,7 +206,7 @@ export class ApiService implements ApiServiceAbstraction {
// Auth APIs // Auth APIs
async postIdentityToken( async postIdentityToken(
request: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest request: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest
): Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse> { ): Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse> {
const headers = new Headers({ const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
@@ -219,7 +219,7 @@ export class ApiService implements ApiServiceAbstraction {
request.alterIdentityTokenHeaders(headers); request.alterIdentityTokenHeaders(headers);
const identityToken = const identityToken =
request instanceof ApiTokenRequest request instanceof UserApiTokenRequest
? request.toIdentityToken() ? request.toIdentityToken()
: request.toIdentityToken(this.platformUtilsService.getClientType()); : request.toIdentityToken(this.platformUtilsService.getClientType());
@@ -2271,8 +2271,7 @@ export class ApiService implements ApiServiceAbstraction {
const appId = await this.appIdService.getAppId(); const appId = await this.appIdService.getAppId();
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
const tokenRequest = new UserApiTokenRequest(
const tokenRequest = new ApiTokenRequest(
clientId, clientId,
clientSecret, clientSecret,
new TokenTwoFactorRequest(), new TokenTwoFactorRequest(),

View File

@@ -17,13 +17,13 @@ import { AuthenticationStatus } from "../enums/authenticationStatus";
import { AuthenticationType } from "../enums/authenticationType"; import { AuthenticationType } from "../enums/authenticationType";
import { KdfType } from "../enums/kdfType"; import { KdfType } from "../enums/kdfType";
import { KeySuffixOptions } from "../enums/keySuffixOptions"; import { KeySuffixOptions } from "../enums/keySuffixOptions";
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy"; import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy"; import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy";
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.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 { AuthResult } from "../models/domain/auth-result";
import { import {
ApiLogInCredentials, UserApiLogInCredentials,
PasswordLogInCredentials, PasswordLogInCredentials,
SsoLogInCredentials, SsoLogInCredentials,
PasswordlessLogInCredentials, PasswordlessLogInCredentials,
@@ -67,7 +67,7 @@ export class AuthService implements AuthServiceAbstraction {
} }
private logInStrategy: private logInStrategy:
| ApiLogInStrategy | UserApiLogInStrategy
| PasswordLogInStrategy | PasswordLogInStrategy
| SsoLogInStrategy | SsoLogInStrategy
| PasswordlessLogInStrategy; | PasswordlessLogInStrategy;
@@ -92,7 +92,7 @@ export class AuthService implements AuthServiceAbstraction {
async logIn( async logIn(
credentials: credentials:
| ApiLogInCredentials | UserApiLogInCredentials
| PasswordLogInCredentials | PasswordLogInCredentials
| SsoLogInCredentials | SsoLogInCredentials
| PasswordlessLogInCredentials | PasswordlessLogInCredentials
@@ -100,7 +100,7 @@ export class AuthService implements AuthServiceAbstraction {
this.clearState(); this.clearState();
let strategy: let strategy:
| ApiLogInStrategy | UserApiLogInStrategy
| PasswordLogInStrategy | PasswordLogInStrategy
| SsoLogInStrategy | SsoLogInStrategy
| PasswordlessLogInStrategy; | PasswordlessLogInStrategy;
@@ -134,8 +134,8 @@ export class AuthService implements AuthServiceAbstraction {
this.keyConnectorService this.keyConnectorService
); );
break; break;
case AuthenticationType.Api: case AuthenticationType.UserApi:
strategy = new ApiLogInStrategy( strategy = new UserApiLogInStrategy(
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@@ -203,8 +203,8 @@ export class AuthService implements AuthServiceAbstraction {
this.messagingService.send("loggedOut"); this.messagingService.send("loggedOut");
} }
authingWithApiKey(): boolean { authingWithUserApiKey(): boolean {
return this.logInStrategy instanceof ApiLogInStrategy; return this.logInStrategy instanceof UserApiLogInStrategy;
} }
authingWithSso(): boolean { authingWithSso(): boolean {
@@ -272,7 +272,7 @@ export class AuthService implements AuthServiceAbstraction {
private saveState( private saveState(
strategy: strategy:
| ApiLogInStrategy | UserApiLogInStrategy
| PasswordLogInStrategy | PasswordLogInStrategy
| SsoLogInStrategy | SsoLogInStrategy
| PasswordlessLogInStrategy | PasswordlessLogInStrategy

View File

@@ -64,6 +64,20 @@ export class FolderService implements InternalFolderServiceAbstraction {
return folders.find((folder) => folder.id === id); return folders.find((folder) => folder.id === id);
} }
/**
* @deprecated For the CLI only
* @param id id of the folder
*/
async getFromState(id: string): Promise<Folder> {
const foldersMap = await this.stateService.getEncryptedFolders();
const folder = foldersMap[id];
if (folder == null) {
return null;
}
return new Folder(folder);
}
/** /**
* @deprecated Only use in CLI! * @deprecated Only use in CLI!
*/ */

View File

@@ -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: `<div
*ngFor="let character of passwordArray; index as i"
[class]="getCharacterClass(character)"
>
<span>{{ character }}</span>
<span *ngIf="showCount" class="tw-whitespace-nowrap tw-text-xs tw-leading-5 tw-text-main">{{
i + 1
}}</span>
</div>`,
})
export class ColorPasswordComponent {
@Input() private password: string = null;
@Input() showCount = false;
characterStyles: Record<CharacterType, string[]> = {
[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;
}
}

View File

@@ -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 {}

View File

@@ -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<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({
props: args,
template: `
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
`,
});
const WrappedTemplate: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({
props: args,
template: `
<div class="tw-max-w-32">
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
</div>
`,
});
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,
};

View File

@@ -0,0 +1 @@
export * from "./color-password.module";

View File

@@ -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 { Component, Inject } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { ButtonModule } from "../button"; import { ButtonModule } from "../button";
import { IconButtonModule } from "../icon-button";
import { SharedModule } from "../shared";
import { I18nMockService } from "../utils/i18n-mock.service"; import { I18nMockService } from "../utils/i18n-mock.service";
import { DialogService } from "./dialog.service"; import { DialogService } from "./dialog.service";
@@ -35,7 +37,7 @@ class StoryDialogComponent {
@Component({ @Component({
selector: "story-dialog-content", selector: "story-dialog-content",
template: ` template: `
<bit-dialog [dialogSize]="large"> <bit-dialog dialogSize="large">
<span bitDialogTitle>Dialog Title</span> <span bitDialogTitle>Dialog Title</span>
<span bitDialogContent> <span bitDialogContent>
Dialog body text goes here. Dialog body text goes here.
@@ -68,7 +70,7 @@ export default {
DialogTitleContainerDirective, DialogTitleContainerDirective,
StoryDialogContentComponent, StoryDialogContentComponent,
], ],
imports: [ButtonModule, DialogModule], imports: [SharedModule, ButtonModule, DialogModule, IconButtonModule],
providers: [ providers: [
DialogService, DialogService,
{ {

View File

@@ -14,4 +14,5 @@ export * from "./multi-select";
export * from "./tabs"; export * from "./tabs";
export * from "./table"; export * from "./table";
export * from "./toggle-group"; export * from "./toggle-group";
export * from "./color-password";
export * from "./utils/i18n-mock.service"; export * from "./utils/i18n-mock.service";

View File

@@ -34,7 +34,7 @@
role="tabpanel" role="tabpanel"
*ngFor="let tab of tabs; let i = index" *ngFor="let tab of tabs; let i = index"
[id]="getTabContentId(i)" [id]="getTabContentId(i)"
[attr.tabindex]="selectedIndex === i ? 0 : -1" [attr.tabindex]="tab.contentTabIndex"
[attr.labeledby]="getTabLabelId(i)" [attr.labeledby]="getTabLabelId(i)"
[active]="tab.isActive" [active]="tab.isActive"
[content]="tab.content" [content]="tab.content"

View File

@@ -20,9 +20,18 @@ import { TabLabelDirective } from "./tab-label.directive";
}) })
export class TabComponent implements OnInit { export class TabComponent implements OnInit {
@Input() disabled = false; @Input() disabled = false;
@Input("label") textLabel = ""; @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<unknown>; @ViewChild(TemplateRef, { static: true }) implicitContent: TemplateRef<unknown>;
@ContentChild(TabLabelDirective) templateLabel: TabLabelDirective; @ContentChild(TabLabelDirective) templateLabel: TabLabelDirective;

View File

@@ -3,6 +3,9 @@ import { Component } from "@angular/core";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; 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 { TabGroupComponent } from "./tab-group/tab-group.component";
import { TabsModule } from "./tabs.module"; import { TabsModule } from "./tabs.module";
@@ -44,6 +47,8 @@ export default {
imports: [ imports: [
CommonModule, CommonModule,
TabsModule, TabsModule,
ButtonModule,
FormFieldModule,
RouterModule.forRoot( RouterModule.forRoot(
[ [
{ path: "", redirectTo: "active", pathMatch: "full" }, { path: "", redirectTo: "active", pathMatch: "full" },
@@ -125,3 +130,32 @@ const PreserveContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) =>
}); });
export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({}); export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({});
const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args,
template: `
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">
<bit-tab label="Form Tab">
<p>
You can navigate through all tab labels, form inputs, and the button that is outside the tab group via
the keyboard.
</p>
<bit-form-field>
<bit-label>First Input</bit-label>
<input type="text" bitInput />
</bit-form-field>
<bit-form-field>
<bit-label>Second Input</bit-label>
<input type="text" bitInput />
</bit-form-field>
</bit-tab>
<bit-tab label="No Focusable Content Tab" [contentTabIndex]="0">
<p>This tab has no focusable content, but the panel should still be focusable</p>
</bit-tab>
</bit-tab-group>
<button bitButton buttonType="primary" class="tw-mt-5">External Button</button>
`,
});
export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({});

View File

@@ -1,6 +1,6 @@
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; 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 = const RsaPublicKey =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +

View File

@@ -21,7 +21,7 @@ import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/models/domain/auth-result";
import { import {
ApiLogInCredentials, UserApiLogInCredentials,
PasswordLogInCredentials, PasswordLogInCredentials,
SsoLogInCredentials, SsoLogInCredentials,
} from "@bitwarden/common/models/domain/log-in-credentials"; } from "@bitwarden/common/models/domain/log-in-credentials";
@@ -160,7 +160,12 @@ export class LoginCommand {
let response: AuthResult = null; let response: AuthResult = null;
if (clientId != null && clientSecret != 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) { } else if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.authService.logIn( response = await this.authService.logIn(
new SsoLogInCredentials( new SsoLogInCredentials(