From c0bb7b9edf54a8b383332bdfb0a6954de044cae2 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 27 May 2024 11:03:23 +1000 Subject: [PATCH 001/138] [AC-2631] Add device-approval command scaffolding (#9351) * Add device-approval scaffolding * Refactor: move helpers to BaseProgram * Update CODEOWNERS --- .github/CODEOWNERS | 1 + apps/cli/src/base-program.ts | 173 ++++++++++++++++++ apps/cli/src/program.ts | 154 +--------------- apps/cli/src/register-oss-programs.ts | 4 +- apps/cli/src/tools/send/send.program.ts | 6 +- apps/cli/src/vault.program.ts | 6 +- .../device-approval/approve-all.command.ts | 9 + .../device-approval/approve.command.ts | 9 + .../device-approval/deny-all.command.ts | 9 + .../device-approval/deny.command.ts | 9 + .../device-approval.program.ts | 96 ++++++++++ .../admin-console/device-approval/index.ts | 1 + .../device-approval/list.command.ts | 9 + .../bit-cli/src/register-bit-programs.ts | 5 +- libs/common/src/enums/feature-flag.enum.ts | 2 + 15 files changed, 333 insertions(+), 160 deletions(-) create mode 100644 apps/cli/src/base-program.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts create mode 100644 bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2ec9d3b1b40..118f9dab277 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -52,6 +52,7 @@ apps/cli/src/admin-console @bitwarden/team-admin-console-dev apps/desktop/src/admin-console @bitwarden/team-admin-console-dev apps/web/src/app/admin-console @bitwarden/team-admin-console-dev bitwarden_license/bit-web/src/app/admin-console @bitwarden/team-admin-console-dev +bitwarden_license/bit-cli/src/admin-console @bitwarden/team-admin-console-dev libs/angular/src/admin-console @bitwarden/team-admin-console-dev libs/common/src/admin-console @bitwarden/team-admin-console-dev libs/admin-console @bitwarden/team-admin-console-dev diff --git a/apps/cli/src/base-program.ts b/apps/cli/src/base-program.ts new file mode 100644 index 00000000000..46aadc323c3 --- /dev/null +++ b/apps/cli/src/base-program.ts @@ -0,0 +1,173 @@ +import * as chalk from "chalk"; +import { firstValueFrom, map } from "rxjs"; + +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +import { UnlockCommand } from "./auth/commands/unlock.command"; +import { Response } from "./models/response"; +import { ListResponse } from "./models/response/list.response"; +import { MessageResponse } from "./models/response/message.response"; +import { StringResponse } from "./models/response/string.response"; +import { TemplateResponse } from "./models/response/template.response"; +import { ServiceContainer } from "./service-container"; +import { CliUtils } from "./utils"; + +const writeLn = CliUtils.writeLn; + +export abstract class BaseProgram { + constructor(protected serviceContainer: ServiceContainer) {} + + protected processResponse(response: Response, exitImmediately = false) { + if (!response.success) { + if (process.env.BW_QUIET !== "true") { + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else { + writeLn(chalk.redBright(response.message), true, true); + } + } + const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; + if (exitImmediately) { + process.exit(exitCode); + } else { + process.exitCode = exitCode; + } + return; + } + + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else if (response.data != null) { + let out: string = null; + + if (response.data.object === "template") { + out = this.getJson((response.data as TemplateResponse).template); + } + + if (out == null) { + if (response.data.object === "string") { + const data = (response.data as StringResponse).data; + if (data != null) { + out = data; + } + } else if (response.data.object === "list") { + out = this.getJson((response.data as ListResponse).data); + } else if (response.data.object === "message") { + out = this.getMessage(response); + } else { + out = this.getJson(response.data); + } + } + + if (out != null && process.env.BW_QUIET !== "true") { + writeLn(out, true, false); + } + } + if (exitImmediately) { + process.exit(0); + } else { + process.exitCode = 0; + } + } + + private getJson(obj: any): string { + if (process.env.BW_PRETTY === "true") { + return JSON.stringify(obj, null, " "); + } else { + return JSON.stringify(obj); + } + } + + protected getMessage(response: Response): string { + const message = response.data as MessageResponse; + if (process.env.BW_RAW === "true") { + return message.raw; + } + + let out = ""; + if (message.title != null) { + if (message.noColor) { + out = message.title; + } else { + out = chalk.greenBright(message.title); + } + } + if (message.message != null) { + if (message.title != null) { + out += "\n"; + } + out += message.message; + } + return out.trim() === "" ? null : out; + } + + protected async exitIfAuthed() { + const authed = await firstValueFrom( + this.serviceContainer.authService.activeAccountStatus$.pipe( + map((status) => status > AuthenticationStatus.LoggedOut), + ), + ); + if (authed) { + const email = await firstValueFrom( + this.serviceContainer.accountService.activeAccount$.pipe(map((a) => a?.email)), + ); + this.processResponse(Response.error("You are already logged in as " + email + "."), true); + } + } + + protected async exitIfNotAuthed() { + const authed = await this.serviceContainer.stateService.getIsAuthenticated(); + if (!authed) { + this.processResponse(Response.error("You are not logged in."), true); + } + } + + protected async exitIfLocked() { + await this.exitIfNotAuthed(); + if (await this.serviceContainer.cryptoService.hasUserKey()) { + return; + } else if (process.env.BW_NOINTERACTION !== "true") { + // must unlock + if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) { + const response = Response.error( + "Your vault is locked. You must unlock your vault using your session key.\n" + + "If you do not have your session key, you can get a new one by logging out and logging in again.", + ); + this.processResponse(response, true); + } else { + const command = new UnlockCommand( + this.serviceContainer.accountService, + this.serviceContainer.masterPasswordService, + this.serviceContainer.cryptoService, + this.serviceContainer.stateService, + this.serviceContainer.cryptoFunctionService, + this.serviceContainer.apiService, + this.serviceContainer.logService, + this.serviceContainer.keyConnectorService, + this.serviceContainer.environmentService, + this.serviceContainer.syncService, + this.serviceContainer.organizationApiService, + this.serviceContainer.logout, + this.serviceContainer.kdfConfigService, + ); + const response = await command.run(null, null); + if (!response.success) { + this.processResponse(response, true); + } + } + } else { + this.processResponse(Response.error("Vault is locked."), true); + } + } + + protected async exitIfFeatureFlagDisabled(featureFlag: FeatureFlag) { + const enabled = await firstValueFrom( + this.serviceContainer.configService.getFeatureFlag$(featureFlag), + ); + + if (!enabled) { + this.processResponse(Response.error("This command is temporarily unavailable."), true); + } + } +} diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 667e0f683fd..e0311beb247 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,6 +1,6 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -8,6 +8,7 @@ import { LockCommand } from "./auth/commands/lock.command"; import { LoginCommand } from "./auth/commands/login.command"; import { LogoutCommand } from "./auth/commands/logout.command"; import { UnlockCommand } from "./auth/commands/unlock.command"; +import { BaseProgram } from "./base-program"; import { CompletionCommand } from "./commands/completion.command"; import { ConfigCommand } from "./commands/config.command"; import { EncodeCommand } from "./commands/encode.command"; @@ -15,20 +16,14 @@ import { ServeCommand } from "./commands/serve.command"; import { StatusCommand } from "./commands/status.command"; import { UpdateCommand } from "./commands/update.command"; import { Response } from "./models/response"; -import { ListResponse } from "./models/response/list.response"; import { MessageResponse } from "./models/response/message.response"; -import { StringResponse } from "./models/response/string.response"; -import { TemplateResponse } from "./models/response/template.response"; -import { ServiceContainer } from "./service-container"; import { GenerateCommand } from "./tools/generate.command"; import { CliUtils } from "./utils"; import { SyncCommand } from "./vault/sync.command"; const writeLn = CliUtils.writeLn; -export class Program { - constructor(protected serviceContainer: ServiceContainer) {} - +export class Program extends BaseProgram { async register() { program .option("--pretty", "Format output. JSON is tabbed with two spaces.") @@ -517,147 +512,4 @@ export class Program { await command.run(cmd); }); } - - protected processResponse(response: Response, exitImmediately = false) { - if (!response.success) { - if (process.env.BW_QUIET !== "true") { - if (process.env.BW_RESPONSE === "true") { - writeLn(this.getJson(response), true, false); - } else { - writeLn(chalk.redBright(response.message), true, true); - } - } - const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; - if (exitImmediately) { - process.exit(exitCode); - } else { - process.exitCode = exitCode; - } - return; - } - - if (process.env.BW_RESPONSE === "true") { - writeLn(this.getJson(response), true, false); - } else if (response.data != null) { - let out: string = null; - - if (response.data.object === "template") { - out = this.getJson((response.data as TemplateResponse).template); - } - - if (out == null) { - if (response.data.object === "string") { - const data = (response.data as StringResponse).data; - if (data != null) { - out = data; - } - } else if (response.data.object === "list") { - out = this.getJson((response.data as ListResponse).data); - } else if (response.data.object === "message") { - out = this.getMessage(response); - } else { - out = this.getJson(response.data); - } - } - - if (out != null && process.env.BW_QUIET !== "true") { - writeLn(out, true, false); - } - } - if (exitImmediately) { - process.exit(0); - } else { - process.exitCode = 0; - } - } - - private getJson(obj: any): string { - if (process.env.BW_PRETTY === "true") { - return JSON.stringify(obj, null, " "); - } else { - return JSON.stringify(obj); - } - } - - private getMessage(response: Response): string { - const message = response.data as MessageResponse; - if (process.env.BW_RAW === "true") { - return message.raw; - } - - let out = ""; - if (message.title != null) { - if (message.noColor) { - out = message.title; - } else { - out = chalk.greenBright(message.title); - } - } - if (message.message != null) { - if (message.title != null) { - out += "\n"; - } - out += message.message; - } - return out.trim() === "" ? null : out; - } - - private async exitIfAuthed() { - const authed = await firstValueFrom( - this.serviceContainer.authService.activeAccountStatus$.pipe( - map((status) => status > AuthenticationStatus.LoggedOut), - ), - ); - if (authed) { - const email = await firstValueFrom( - this.serviceContainer.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - this.processResponse(Response.error("You are already logged in as " + email + "."), true); - } - } - - private async exitIfNotAuthed() { - const authed = await this.serviceContainer.stateService.getIsAuthenticated(); - if (!authed) { - this.processResponse(Response.error("You are not logged in."), true); - } - } - - protected async exitIfLocked() { - await this.exitIfNotAuthed(); - if (await this.serviceContainer.cryptoService.hasUserKey()) { - return; - } else if (process.env.BW_NOINTERACTION !== "true") { - // must unlock - if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) { - const response = Response.error( - "Your vault is locked. You must unlock your vault using your session key.\n" + - "If you do not have your session key, you can get a new one by logging out and logging in again.", - ); - this.processResponse(response, true); - } else { - const command = new UnlockCommand( - this.serviceContainer.accountService, - this.serviceContainer.masterPasswordService, - this.serviceContainer.cryptoService, - this.serviceContainer.stateService, - this.serviceContainer.cryptoFunctionService, - this.serviceContainer.apiService, - this.serviceContainer.logService, - this.serviceContainer.keyConnectorService, - this.serviceContainer.environmentService, - this.serviceContainer.syncService, - this.serviceContainer.organizationApiService, - this.serviceContainer.logout, - this.serviceContainer.kdfConfigService, - ); - const response = await command.run(null, null); - if (!response.success) { - this.processResponse(response, true); - } - } - } else { - this.processResponse(Response.error("Vault is locked."), true); - } - } } diff --git a/apps/cli/src/register-oss-programs.ts b/apps/cli/src/register-oss-programs.ts index f47aa528543..d8aa54118d7 100644 --- a/apps/cli/src/register-oss-programs.ts +++ b/apps/cli/src/register-oss-programs.ts @@ -15,8 +15,8 @@ export async function registerOssPrograms(serviceContainer: ServiceContainer) { await program.register(); const vaultProgram = new VaultProgram(serviceContainer); - await vaultProgram.register(); + vaultProgram.register(); const sendProgram = new SendProgram(serviceContainer); - await sendProgram.register(); + sendProgram.register(); } diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 86edd28f098..670683e7a2a 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -7,9 +7,9 @@ import { program, Command, OptionValues } from "commander"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { BaseProgram } from "../../base-program"; import { GetCommand } from "../../commands/get.command"; import { Response } from "../../models/response"; -import { Program } from "../../program"; import { CliUtils } from "../../utils"; import { @@ -27,8 +27,8 @@ import { SendResponse } from "./models/send.response"; const writeLn = CliUtils.writeLn; -export class SendProgram extends Program { - async register() { +export class SendProgram extends BaseProgram { + register() { program.addCommand(this.sendCommand()); // receive is accessible both at `bw receive` and `bw send receive` program.addCommand(this.receiveCommand()); diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 52857ed5424..c8e0701845b 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -2,12 +2,12 @@ import { program, Command } from "commander"; import { ConfirmCommand } from "./admin-console/commands/confirm.command"; import { ShareCommand } from "./admin-console/commands/share.command"; +import { BaseProgram } from "./base-program"; import { EditCommand } from "./commands/edit.command"; import { GetCommand } from "./commands/get.command"; import { ListCommand } from "./commands/list.command"; import { RestoreCommand } from "./commands/restore.command"; import { Response } from "./models/response"; -import { Program } from "./program"; import { ExportCommand } from "./tools/export.command"; import { ImportCommand } from "./tools/import.command"; import { CliUtils } from "./utils"; @@ -16,8 +16,8 @@ import { DeleteCommand } from "./vault/delete.command"; const writeLn = CliUtils.writeLn; -export class VaultProgram extends Program { - async register() { +export class VaultProgram extends BaseProgram { + register() { program .addCommand(this.listCommand()) .addCommand(this.getCommand()) diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts new file mode 100644 index 00000000000..a3a6c4943f8 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts @@ -0,0 +1,9 @@ +import { Response } from "@bitwarden/cli/models/response"; + +export class ApproveAllCommand { + constructor() {} + + async run(organizationId: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts new file mode 100644 index 00000000000..b3a30165ce3 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -0,0 +1,9 @@ +import { Response } from "@bitwarden/cli/models/response"; + +export class ApproveCommand { + constructor() {} + + async run(id: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts new file mode 100644 index 00000000000..521a7e8ded6 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -0,0 +1,9 @@ +import { Response } from "@bitwarden/cli/models/response"; + +export class DenyAllCommand { + constructor() {} + + async run(organizationId: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts new file mode 100644 index 00000000000..a366bfb05a0 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -0,0 +1,9 @@ +import { Response } from "@bitwarden/cli/models/response"; + +export class DenyCommand { + constructor() {} + + async run(id: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts new file mode 100644 index 00000000000..342ea9bc52e --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts @@ -0,0 +1,96 @@ +import { program, Command } from "commander"; + +import { BaseProgram } from "@bitwarden/cli/base-program"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +import { ApproveAllCommand } from "./approve-all.command"; +import { ApproveCommand } from "./approve.command"; +import { DenyAllCommand } from "./deny-all.command"; +import { DenyCommand } from "./deny.command"; +import { ListCommand } from "./list.command"; + +export class DeviceApprovalProgram extends BaseProgram { + register() { + program.addCommand(this.deviceApprovalCommand()); + } + + private deviceApprovalCommand() { + return new Command("device-approval") + .description("Manage device approvals") + .addCommand(this.listCommand()) + .addCommand(this.approveCommand()) + .addCommand(this.approveAllCommand()) + .addCommand(this.denyCommand()) + .addCommand(this.denyAllCommand()); + } + + private listCommand(): Command { + return new Command("list") + .description("List all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfNotAuthed(); + + const cmd = new ListCommand(); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } + + private approveCommand(): Command { + return new Command("approve") + .argument("") + .description("Approve a pending request") + .action(async (id: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new ApproveCommand(); + const response = await cmd.run(id); + this.processResponse(response); + }); + } + + private approveAllCommand(): Command { + return new Command("approveAll") + .description("Approve all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new ApproveAllCommand(); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } + + private denyCommand(): Command { + return new Command("deny") + .argument("") + .description("Deny a pending request") + .action(async (id: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new DenyCommand(); + const response = await cmd.run(id); + this.processResponse(response); + }); + } + + private denyAllCommand(): Command { + return new Command("denyAll") + .description("Deny all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new DenyAllCommand(); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts new file mode 100644 index 00000000000..399f89623ec --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts @@ -0,0 +1 @@ +export { DeviceApprovalProgram } from "./device-approval.program"; diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts new file mode 100644 index 00000000000..11fb6ec3ee2 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts @@ -0,0 +1,9 @@ +import { Response } from "@bitwarden/cli/models/response"; + +export class ListCommand { + constructor() {} + + async run(organizationId: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/bitwarden_license/bit-cli/src/register-bit-programs.ts b/bitwarden_license/bit-cli/src/register-bit-programs.ts index 859574644ad..0d3f5e39f25 100644 --- a/bitwarden_license/bit-cli/src/register-bit-programs.ts +++ b/bitwarden_license/bit-cli/src/register-bit-programs.ts @@ -1,3 +1,4 @@ +import { DeviceApprovalProgram } from "./admin-console/device-approval"; import { ServiceContainer } from "./service-container"; /** @@ -7,4 +8,6 @@ import { ServiceContainer } from "./service-container"; * myProgram.register(); * @param serviceContainer A class that instantiates services and makes them available for dependency injection */ -export async function registerBitPrograms(serviceContainer: ServiceContainer) {} +export async function registerBitPrograms(serviceContainer: ServiceContainer) { + new DeviceApprovalProgram(serviceContainer).register(); +} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index af022f4e549..f3bea6964ec 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -16,6 +16,7 @@ export enum FeatureFlag { ExtensionRefresh = "extension-refresh", RestrictProviderAccess = "restrict-provider-access", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", + BulkDeviceApproval = "bulk-device-approval", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -42,6 +43,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.RestrictProviderAccess]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, + [FeatureFlag.BulkDeviceApproval]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; From c61ba41b97d53eb20b5a45e6b6ae6487533a115d Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 27 May 2024 08:12:28 -0400 Subject: [PATCH 002/138] Use User Key Definitions for user-scoped data (#9348) --- .../services/vault-browser-state.service.ts | 23 +++++++++++-------- .../services/vault-banners.service.ts | 8 ++++--- .../services/vault-onboarding.service.ts | 13 +++++++---- .../src/vault/services/collection.service.ts | 5 ++-- .../vault/services/key-state/ciphers.state.ts | 14 +++++++---- .../key-state/collapsed-groupings.state.ts | 5 ++-- .../vault/services/key-state/folder.state.ts | 13 +++++++---- .../key-state/vault-settings.state.ts | 12 ++++++---- 8 files changed, 59 insertions(+), 34 deletions(-) diff --git a/apps/browser/src/vault/services/vault-browser-state.service.ts b/apps/browser/src/vault/services/vault-browser-state.service.ts index 43a28928da5..17934b3867f 100644 --- a/apps/browser/src/vault/services/vault-browser-state.service.ts +++ b/apps/browser/src/vault/services/vault-browser-state.service.ts @@ -3,28 +3,31 @@ import { Jsonify } from "type-fest"; import { ActiveUserState, - KeyDefinition, StateProvider, + UserKeyDefinition, VAULT_BROWSER_MEMORY, } from "@bitwarden/common/platform/state"; import { BrowserComponentState } from "../../models/browserComponentState"; import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; -export const VAULT_BROWSER_GROUPINGS_COMPONENT = new KeyDefinition( - VAULT_BROWSER_MEMORY, - "vault_browser_groupings_component", - { - deserializer: (obj: Jsonify) => - BrowserGroupingsComponentState.fromJSON(obj), - }, -); +export const VAULT_BROWSER_GROUPINGS_COMPONENT = + new UserKeyDefinition( + VAULT_BROWSER_MEMORY, + "vault_browser_groupings_component", + { + deserializer: (obj: Jsonify) => + BrowserGroupingsComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], + }, + ); -export const VAULT_BROWSER_COMPONENT = new KeyDefinition( +export const VAULT_BROWSER_COMPONENT = new UserKeyDefinition( VAULT_BROWSER_MEMORY, "vault_browser_component", { deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], }, ); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index b54f2e9c115..1cb8e13cb3e 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -11,9 +11,9 @@ import { KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums"; import { StateProvider, ActiveUserState, - KeyDefinition, PREMIUM_BANNER_DISK_LOCAL, BANNERS_DISMISSED_DISK, + UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -33,19 +33,21 @@ type PremiumBannerReprompt = { /** Banners that will be re-shown on a new session */ type SessionBanners = Omit; -export const PREMIUM_BANNER_REPROMPT_KEY = new KeyDefinition( +export const PREMIUM_BANNER_REPROMPT_KEY = new UserKeyDefinition( PREMIUM_BANNER_DISK_LOCAL, "bannerReprompt", { deserializer: (bannerReprompt) => bannerReprompt, + clearOn: [], // Do not clear user tutorials }, ); -export const BANNERS_DISMISSED_DISK_KEY = new KeyDefinition( +export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition( BANNERS_DISMISSED_DISK, "bannersDismissed", { deserializer: (bannersDismissed) => bannersDismissed, + clearOn: [], // Do not clear user tutorials }, ); diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts index 927de00737c..4f791a1a2d9 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts @@ -3,8 +3,8 @@ import { Observable } from "rxjs"; import { ActiveUserState, - KeyDefinition, StateProvider, + UserKeyDefinition, VAULT_ONBOARDING, } from "@bitwarden/common/platform/state"; @@ -16,9 +16,14 @@ export type VaultOnboardingTasks = { installExtension: boolean; }; -const VAULT_ONBOARDING_KEY = new KeyDefinition(VAULT_ONBOARDING, "tasks", { - deserializer: (jsonData) => jsonData, -}); +const VAULT_ONBOARDING_KEY = new UserKeyDefinition( + VAULT_ONBOARDING, + "tasks", + { + deserializer: (jsonData) => jsonData, + clearOn: [], // do not clear tutorials + }, +); @Injectable() export class VaultOnboardingService implements VaultOnboardingServiceAbstraction { diff --git a/libs/common/src/vault/services/collection.service.ts b/libs/common/src/vault/services/collection.service.ts index 2c91651a0e0..97b3257ab56 100644 --- a/libs/common/src/vault/services/collection.service.ts +++ b/libs/common/src/vault/services/collection.service.ts @@ -6,11 +6,11 @@ import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; import { ActiveUserState, - KeyDefinition, StateProvider, COLLECTION_DATA, DeriveDefinition, DerivedState, + UserKeyDefinition, } from "../../platform/state"; import { CollectionId, OrganizationId, UserId } from "../../types/guid"; import { CollectionService as CollectionServiceAbstraction } from "../../vault/abstractions/collection.service"; @@ -20,11 +20,12 @@ import { TreeNode } from "../models/domain/tree-node"; import { CollectionView } from "../models/view/collection.view"; import { ServiceUtils } from "../service-utils"; -const ENCRYPTED_COLLECTION_DATA_KEY = KeyDefinition.record( +const ENCRYPTED_COLLECTION_DATA_KEY = UserKeyDefinition.record( COLLECTION_DATA, "collections", { deserializer: (jsonData: Jsonify) => CollectionData.fromJSON(jsonData), + clearOn: ["logout"], }, ); diff --git a/libs/common/src/vault/services/key-state/ciphers.state.ts b/libs/common/src/vault/services/key-state/ciphers.state.ts index 71da4c23334..ff6b4fb051b 100644 --- a/libs/common/src/vault/services/key-state/ciphers.state.ts +++ b/libs/common/src/vault/services/key-state/ciphers.state.ts @@ -4,7 +4,7 @@ import { CIPHERS_DISK, CIPHERS_DISK_LOCAL, CIPHERS_MEMORY, - KeyDefinition, + UserKeyDefinition, } from "../../../platform/state"; import { CipherId } from "../../../types/guid"; import { CipherData } from "../../models/data/cipher.data"; @@ -12,27 +12,30 @@ import { LocalData } from "../../models/data/local.data"; import { CipherView } from "../../models/view/cipher.view"; import { AddEditCipherInfo } from "../../types/add-edit-cipher-info"; -export const ENCRYPTED_CIPHERS = KeyDefinition.record(CIPHERS_DISK, "ciphers", { +export const ENCRYPTED_CIPHERS = UserKeyDefinition.record(CIPHERS_DISK, "ciphers", { deserializer: (obj: Jsonify) => CipherData.fromJSON(obj), + clearOn: ["logout"], }); -export const DECRYPTED_CIPHERS = KeyDefinition.record( +export const DECRYPTED_CIPHERS = UserKeyDefinition.record( CIPHERS_MEMORY, "decryptedCiphers", { deserializer: (cipher: Jsonify) => CipherView.fromJSON(cipher), + clearOn: ["logout", "lock"], }, ); -export const LOCAL_DATA_KEY = new KeyDefinition>( +export const LOCAL_DATA_KEY = new UserKeyDefinition>( CIPHERS_DISK_LOCAL, "localData", { deserializer: (localData) => localData, + clearOn: ["logout"], }, ); -export const ADD_EDIT_CIPHER_INFO_KEY = new KeyDefinition( +export const ADD_EDIT_CIPHER_INFO_KEY = new UserKeyDefinition( CIPHERS_MEMORY, "addEditCipherInfo", { @@ -48,5 +51,6 @@ export const ADD_EDIT_CIPHER_INFO_KEY = new KeyDefinition( return { cipher, collectionIds: addEditCipherInfo.collectionIds }; }, + clearOn: ["logout", "lock"], }, ); diff --git a/libs/common/src/vault/services/key-state/collapsed-groupings.state.ts b/libs/common/src/vault/services/key-state/collapsed-groupings.state.ts index b1b976770e1..b6d48874175 100644 --- a/libs/common/src/vault/services/key-state/collapsed-groupings.state.ts +++ b/libs/common/src/vault/services/key-state/collapsed-groupings.state.ts @@ -1,9 +1,10 @@ -import { KeyDefinition, VAULT_FILTER_DISK } from "../../../platform/state"; +import { UserKeyDefinition, VAULT_FILTER_DISK } from "../../../platform/state"; -export const COLLAPSED_GROUPINGS = KeyDefinition.array( +export const COLLAPSED_GROUPINGS = UserKeyDefinition.array( VAULT_FILTER_DISK, "collapsedGroupings", { deserializer: (obj) => obj, + clearOn: ["logout", "lock"], }, ); diff --git a/libs/common/src/vault/services/key-state/folder.state.ts b/libs/common/src/vault/services/key-state/folder.state.ts index 7fbd63ed76d..1a45c88d6f2 100644 --- a/libs/common/src/vault/services/key-state/folder.state.ts +++ b/libs/common/src/vault/services/key-state/folder.state.ts @@ -1,15 +1,20 @@ import { Jsonify } from "type-fest"; import { CryptoService } from "../../../platform/abstractions/crypto.service"; -import { DeriveDefinition, FOLDER_DISK, KeyDefinition } from "../../../platform/state"; +import { DeriveDefinition, FOLDER_DISK, UserKeyDefinition } from "../../../platform/state"; import { FolderService } from "../../abstractions/folder/folder.service.abstraction"; import { FolderData } from "../../models/data/folder.data"; import { Folder } from "../../models/domain/folder"; import { FolderView } from "../../models/view/folder.view"; -export const FOLDER_ENCRYPTED_FOLDERS = KeyDefinition.record(FOLDER_DISK, "folders", { - deserializer: (obj: Jsonify) => FolderData.fromJSON(obj), -}); +export const FOLDER_ENCRYPTED_FOLDERS = UserKeyDefinition.record( + FOLDER_DISK, + "folders", + { + deserializer: (obj: Jsonify) => FolderData.fromJSON(obj), + clearOn: ["logout"], + }, +); export const FOLDER_DECRYPTED_FOLDERS = DeriveDefinition.from< Record, diff --git a/libs/common/src/vault/services/key-state/vault-settings.state.ts b/libs/common/src/vault/services/key-state/vault-settings.state.ts index 90b47912eee..21364bbbf8e 100644 --- a/libs/common/src/vault/services/key-state/vault-settings.state.ts +++ b/libs/common/src/vault/services/key-state/vault-settings.state.ts @@ -1,4 +1,4 @@ -import { VAULT_SETTINGS_DISK, KeyDefinition } from "../../../platform/state"; +import { VAULT_SETTINGS_DISK, KeyDefinition, UserKeyDefinition } from "../../../platform/state"; export const USER_ENABLE_PASSKEYS = new KeyDefinition( VAULT_SETTINGS_DISK, @@ -8,16 +8,20 @@ export const USER_ENABLE_PASSKEYS = new KeyDefinition( }, ); -export const SHOW_CARDS_CURRENT_TAB = new KeyDefinition( +export const SHOW_CARDS_CURRENT_TAB = new UserKeyDefinition( VAULT_SETTINGS_DISK, "showCardsCurrentTab", { deserializer: (obj) => obj, + clearOn: [], // do not clear user settings }, ); -export const SHOW_IDENTITIES_CURRENT_TAB = new KeyDefinition( +export const SHOW_IDENTITIES_CURRENT_TAB = new UserKeyDefinition( VAULT_SETTINGS_DISK, "showIdentitiesCurrentTab", - { deserializer: (obj) => obj }, + { + deserializer: (obj) => obj, + clearOn: [], // do not clear user settings + }, ); From 2e6280ce88274455155c464b05f1d1dcb7b22a17 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 27 May 2024 08:12:53 -0400 Subject: [PATCH 003/138] Prefer UserKeyDefinition for user-scoped data (#9349) --- apps/browser/src/tools/popup/services/key-definitions.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/tools/popup/services/key-definitions.ts b/apps/browser/src/tools/popup/services/key-definitions.ts index 9b256073f3f..b4ccd991e7b 100644 --- a/apps/browser/src/tools/popup/services/key-definitions.ts +++ b/apps/browser/src/tools/popup/services/key-definitions.ts @@ -1,23 +1,25 @@ import { Jsonify } from "type-fest"; -import { BROWSER_SEND_MEMORY, KeyDefinition } from "@bitwarden/common/platform/state"; +import { BROWSER_SEND_MEMORY, UserKeyDefinition } from "@bitwarden/common/platform/state"; import { BrowserComponentState } from "../../../models/browserComponentState"; import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; -export const BROWSER_SEND_COMPONENT = new KeyDefinition( +export const BROWSER_SEND_COMPONENT = new UserKeyDefinition( BROWSER_SEND_MEMORY, "browser_send_component", { deserializer: (obj: Jsonify) => BrowserSendComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], }, ); -export const BROWSER_SEND_TYPE_COMPONENT = new KeyDefinition( +export const BROWSER_SEND_TYPE_COMPONENT = new UserKeyDefinition( BROWSER_SEND_MEMORY, "browser_send_type_component", { deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], }, ); From 93b9eba76925c95b470a4f7e8b33cef09fbd2a55 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 27 May 2024 08:54:24 -0400 Subject: [PATCH 004/138] Separate browser foreground and background memory space (#9315) * Separate browser foreground and background memory space * Prefer inherited DI providers --- apps/browser/src/popup/services/services.module.ts | 10 ++-------- libs/common/src/auth/services/auth.service.ts | 6 +++--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index a4f5c8a4c67..d260b4ec7b1 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -25,7 +25,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; @@ -34,7 +34,6 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -174,12 +173,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BaseUnauthGuardService, useClass: UnauthGuardService, - deps: [AuthServiceAbstraction, Router], - }), - safeProvider({ - provide: AuthServiceAbstraction, - useFactory: getBgService("authService"), - deps: [], + deps: [AuthService, Router], }), safeProvider({ provide: SsoLoginServiceAbstraction, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index a4529084a2a..25e7b92edf2 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,8 +11,8 @@ import { import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; -import { MessagingService } from "../../platform/abstractions/messaging.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { MessageSender } from "../../platform/messaging"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; import { AccountService } from "../abstractions/account.service"; @@ -26,7 +26,7 @@ export class AuthService implements AuthServiceAbstraction { constructor( protected accountService: AccountService, - protected messagingService: MessagingService, + protected messageSender: MessageSender, protected cryptoService: CryptoService, protected apiService: ApiService, protected stateService: StateService, @@ -95,6 +95,6 @@ export class AuthService implements AuthServiceAbstraction { logOut(callback: () => void) { callback(); - this.messagingService.send("loggedOut"); + this.messageSender.send("loggedOut"); } } From 8e7a215597e79263dc630746aa773551b4b43937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 27 May 2024 14:20:44 +0100 Subject: [PATCH 005/138] =?UTF-8?q?[AC-1712]=C2=A0Add=20functionality=20to?= =?UTF-8?q?=20approve=20all=20device=20approval=20requests=20(#9251)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [AC-2302] Move organization-auth-request.service to bit-common folder * [AC-2302] Rename organization-auth-request.service to organization-auth-request-api.service * [AC-2302] Move logic from component to organization-auth-request.service * [AC-2302] Fix import path in OrganizationAuthRequestService * [AC-2302] Move imports to OrganizationsModule and delete unused CoreOrganizationModule * [AC-2302] Move the call to get userResetPasswordDetails into OrganizationAuthRequestService * [AC-2302] Remove @Injectable() and manually configure dependencies * [AC-2302] Add OrganizationAuthRequestService unit tests first draft * [AC-2302] Refactor device-approvals.component.ts to remove unused imports * [AC-2302] Set up jest on bit-common and add unit tests for OrganizationAuthRequestService * [AC-2302] Add bit-common to jest.config.js * [AC-2302] Update organizations.module.ts to include safeProviders declared in variable * [AC-2302] Remove services and views folders from bit-common * [AC-2302] Define path mapping * Adjust an import path The import path of `PendingAuthRequestView` in `OrganizationAuthRequestApiService` was pointing to the wrong place. I think this file was just recently moved, and the import didn't get updated. * Get paths working * Fix import * Update jest config to use ts-jest adn jsdom * Copy-paste path mappings from bit-web * Remove unnecessary test setup file * Undo unnecessary change * Fix remaining path mappings * Remove Bitwarden License mapping from OSS code * Fix bit-web so it uses its own tsconfig * Fix import path * Remove web-bit entrypoint from OSS tsconfig * Make DeviceApprovalsComponent standalone * Remove organization-auth-request-api.service export * Add BulkApproveAuthRequestsRequest class for bulk approval of authentication requests * Add api call for device bulk approvals * Add bulk device approval to OrganizationAuthRequestService * Add unit tests for bulk device approval method * Remove OrganizationsRoutingModule from DeviceApprovalsComponent imports * Remove CoreOrganizationModule from OrganizationsModule imports * Remove NoItemsModule from OrganizationsModule imports * Get keys for each item to approve * Update approvePendingRequests unit test * Use ApiService from JslibServicesModule * Update providers in device-approvals.component.ts * Add method to retrieve reset password details for multiple organization users * Add organizationUserId property to OrganizationUserResetPasswordDetailsResponse class * Use method to retrieve reset password details for multiple organization users * Rename ResetPasswordDetails to AccountRecoveryDetails * Update OrganizationAuthRequestService to use getManyOrganizationUserAccountRecoveryDetails * Add functionality to approve all device approval requests * Update AdminAuthRequestUpdateWithIdRequest property names and imports * Refactor bulk approval functionality in organization auth requests * Put the 'Approve All' button behind a feature flag. * Update feature flag key Co-authored-by: Addison Beck * Rename update request AdminAuthRequestUpdateWithIdRequest to OrganizationAuthRequestUpdateRequest * Update organization-auth-request.service.spec.ts to use bulkUpdatePendingRequests method --------- Co-authored-by: Addison Beck Co-authored-by: Thomas Rittson Co-authored-by: Addison Beck --- apps/web/src/locales/en/messages.json | 6 +++++ .../device-approvals.component.html | 10 ++++++++ .../device-approvals.component.ts | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index d4d3fc6d815..c21955179d6 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8288,5 +8288,11 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html index 5edc1f58674..cfc6538dbd9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html @@ -28,6 +28,16 @@ appA11yTitle="{{ 'options' | i18n }}" > + + + diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts new file mode 100644 index 00000000000..16759057ed5 --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts @@ -0,0 +1,40 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { Router, RouterLink } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import { ImportComponent } from "@bitwarden/importer/ui"; + +import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; + +@Component({ + templateUrl: "import-browser-v2.component.html", + standalone: true, + imports: [ + CommonModule, + RouterLink, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + PopupPageComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopOutComponent, + ], +}) +export class ImportBrowserV2Component { + protected disabled = false; + protected loading = false; + + constructor(private router: Router) {} + + protected async onSuccessfulImport(organizationId: string): Promise { + await this.router.navigate(["/vault-settings"]); + } +} From 1b5faae7c3a7f550e1ffd4e2b404e0182b99aaae Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Tue, 28 May 2024 21:23:54 +0530 Subject: [PATCH 010/138] [PM-5011][PM-7284] migrate user subscription along with update license dialog (#8659) * migrate user subscription along with update license dialog * migrate user subscription along with update license dialog --- .../user-subscription.component.html | 90 ++++++------------- .../individual/user-subscription.component.ts | 21 +++-- .../billing/shared/billing-shared.module.ts | 3 + .../update-license-dialog.component.html | 40 +++++++++ .../shared/update-license-dialog.component.ts | 38 ++++++++ 5 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 apps/web/src/app/billing/shared/update-license-dialog.component.html create mode 100644 apps/web/src/app/billing/shared/update-license-dialog.component.ts diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index bed41c8dc5c..5f9e6463f1b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -1,10 +1,10 @@ - {{ "loading" | i18n }} + {{ "loading" | i18n }} -

{{ "subscriptionPendingCanceled" | i18n }}

+

{{ "subscriptionPendingCanceled" | i18n }}

@@ -39,12 +37,12 @@
{{ sub.expiration | date: "mediumDate" }}
{{ "neverExpires" | i18n }}
-
-
+
+
{{ "status" | i18n }}
- {{ (subscription && subscription.status) || "-" }} + {{ (subscription && subscription.status) || "-" }} {{ "pendingCancellation" | i18n }} @@ -61,19 +59,19 @@
-
- {{ "details" | i18n }} - - +
+ {{ "details" | i18n }} + +
- - + - -
+ {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ {{ i.amount | currency: "$" }} {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
+ +
@@ -91,27 +89,9 @@ {{ "launchCloudSubscription" | i18n }}
-
-
- -

{{ "updateLicense" | i18n }}

- - -
-
-
+
-

{{ "storage" | i18n }}

-

{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}

-
-
- {{ storagePercentage / 100 | percent }} -
-
+

{{ "storage" | i18n }}

+

+ {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }} +

+ -
-
- -
diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index fa21317c180..8535f23f820 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -20,6 +20,10 @@ import { OffboardingSurveyDialogResultType, openOffboardingSurvey, } from "../shared/offboarding-survey.component"; +import { + UpdateLicenseDialogComponent, + UpdateLicenseDialogResult, +} from "../shared/update-license-dialog.component"; @Component({ templateUrl: "user-subscription.component.html", @@ -131,21 +135,16 @@ export class UserSubscriptionComponent implements OnInit { }); } - updateLicense() { + updateLicense = async () => { if (this.loading) { return; } - this.showUpdateLicense = true; - } - - closeUpdateLicense(load: boolean) { - this.showUpdateLicense = false; - if (load) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); + const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === UpdateLicenseDialogResult.Updated) { + await this.load(); } - } + }; adjustStorage = (add: boolean) => { return async () => { diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 35fe33c7e06..0031cff7755 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -12,6 +12,7 @@ import { PaymentMethodComponent } from "./payment-method.component"; import { PaymentComponent } from "./payment.component"; import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component"; import { TaxInfoComponent } from "./tax-info.component"; +import { UpdateLicenseDialogComponent } from "./update-license-dialog.component"; import { UpdateLicenseComponent } from "./update-license.component"; @NgModule({ @@ -24,6 +25,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; PaymentMethodComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], exports: [ @@ -34,6 +36,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], }) diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.html b/apps/web/src/app/billing/shared/update-license-dialog.component.html new file mode 100644 index 00000000000..6430c47528f --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.html @@ -0,0 +1,40 @@ +
+ + + + {{ "licenseFile" | i18n }} +
+ + {{ licenseFile ? licenseFile.name : ("noFileChosen" | i18n) }} +
+ + {{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }} +
+
+ + + + +
+
diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts new file mode 100644 index 00000000000..5f9a1e94bef --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { UpdateLicenseComponent } from "./update-license.component"; + +export enum UpdateLicenseDialogResult { + Updated = "updated", + Cancelled = "cancelled", +} +@Component({ + templateUrl: "update-license-dialog.component.html", +}) +export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { + constructor( + apiService: ApiService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + organizationApiService: OrganizationApiServiceAbstraction, + formBuilder: FormBuilder, + ) { + super(apiService, i18nService, platformUtilsService, organizationApiService, formBuilder); + } + async submitLicense() { + await this.submit(); + } + submitLicenseDialog = async () => { + await this.submitLicense(); + }; + static open(dialogService: DialogService) { + return dialogService.open(UpdateLicenseDialogComponent); + } +} From 5a25024f591d590d6a1c297b97cf3168ff6fb96c Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 28 May 2024 17:38:15 +0100 Subject: [PATCH 011/138] Change Addons to Add-ons (#9392) --- apps/web/src/locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c21955179d6..e090f6a7ab5 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -2102,7 +2102,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" From 3fc2570a0e2437242b880fd1662f31f573ab11c9 Mon Sep 17 00:00:00 2001 From: DanHillesheim <79476558+DanHillesheim@users.noreply.github.com> Date: Tue, 28 May 2024 10:42:53 -0600 Subject: [PATCH 012/138] Trial initiation content updates (#9138) --- .../content/enterprise-content.component.html | 73 +++++++++++------- .../content/logo-badges.component.html | 11 +++ .../content/logo-badges.component.ts | 7 ++ .../content/teams1-content.component.html | 43 +++++++---- .../trial-initiation.module.ts | 2 + .../register-layout/vault-signup-badges.png | Bin 0 -> 20376 bytes 6 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html create mode 100644 apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts create mode 100644 apps/web/src/images/register-layout/vault-signup-badges.png diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html index 120748d4c0d..4abb44db4f0 100644 --- a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html @@ -1,34 +1,51 @@ -

The Password Manager Trusted by Millions

-
-

Everything enterprises need out of a password manager:

+

Start your 7-day free trial of Bitwarden

+
+

+ Strengthen business security with the password manager designed for seamless administration and + employee usability. +

    -
  • Secure password sharing
  • -
  • - Easy, flexible SSO and SCIM integrations +
  • + Instantly and securely share credentials with the groups and individuals who need them +
  • +
  • + Strengthen employee security practices through centralized administrative control and + policies +
  • +
  • + Streamline user onboarding and automate account provisioning with turnkey SSO and SCIM + integrations +
  • +
  • + Migrate to Bitwarden in minutes with comprehensive import options +
  • +
  • + Save time and increase productivity with autofill and instant device syncing +
  • +
  • + Empower employees to secure their digital life at home, at work, and on the go by offering a + free Families plan to all Enterprise users
  • -
  • Free families plan for users
  • -
  • Quick import and migration tools
  • -
  • Simple, streamlined user experience
  • -
  • Priority support and trainers
- -
- - - -
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html new file mode 100644 index 00000000000..d1b33eab3a4 --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html @@ -0,0 +1,11 @@ +
+
+ + third party awards + +
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts new file mode 100644 index 00000000000..c23432b67cf --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "app-logo-badges", + templateUrl: "logo-badges.component.html", +}) +export class LogoBadgesComponent {} diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html index d26bbabaef2..42f99be26b8 100644 --- a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html @@ -1,21 +1,36 @@ -

Start Your Teams Free Trial Now

-
-
$4 per month / per user
-
Annual subscription
-
+

Start your 7-day free trial for Teams

+

- Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. + Strengthen business security with an easy-to-use password manager your team will love.

-