From 82553ebb13a5a24a444f31b98ced5730d71dd6e1 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 18 Oct 2023 12:14:16 -0400 Subject: [PATCH 01/79] fix typo (#6627) --- ...fido2-authenticator.service.abstraction.ts | 6 ++-- .../fido2/fido2-authenticator.service.spec.ts | 30 +++++++++---------- .../fido2/fido2-authenticator.service.ts | 30 +++++++++---------- .../fido2/fido2-client.service.spec.ts | 8 ++--- .../services/fido2/fido2-client.service.ts | 12 ++++---- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/libs/common/src/vault/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/vault/abstractions/fido2/fido2-authenticator.service.abstraction.ts index 5a406aeb14c..671c6cb9fe1 100644 --- a/libs/common/src/vault/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -39,7 +39,7 @@ export enum Fido2AlgorithmIdentifier { RS256 = -257, } -export enum Fido2AutenticatorErrorCode { +export enum Fido2AuthenticatorErrorCode { Unknown = "UnknownError", NotSupported = "NotSupportedError", InvalidState = "InvalidStateError", @@ -47,8 +47,8 @@ export enum Fido2AutenticatorErrorCode { Constraint = "ConstraintError", } -export class Fido2AutenticatorError extends Error { - constructor(readonly errorCode: Fido2AutenticatorErrorCode) { +export class Fido2AuthenticatorError extends Error { + constructor(readonly errorCode: Fido2AuthenticatorErrorCode) { super(errorCode); } } diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts index c519fccffc2..ecf6f03d7e4 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts @@ -5,7 +5,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { Utils } from "../../../platform/misc/utils"; import { CipherService } from "../../abstractions/cipher.service"; import { - Fido2AutenticatorErrorCode, + Fido2AuthenticatorErrorCode, Fido2AuthenticatorGetAssertionParams, Fido2AuthenticatorMakeCredentialsParams, } from "../../abstractions/fido2/fido2-authenticator.service.abstraction"; @@ -60,19 +60,19 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(invalidParams.unsupportedAlgorithm, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotSupported); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotSupported); }); it("should throw error when requireResidentKey has invalid value", async () => { const result = async () => await authenticator.makeCredential(invalidParams.invalidRk, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); it("should throw error when requireUserVerification has invalid value", async () => { const result = async () => await authenticator.makeCredential(invalidParams.invalidUv, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); /** @@ -85,7 +85,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Constraint); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Constraint); }); it("should not request confirmation from user", async () => { @@ -151,7 +151,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); /** Devation: Organization ciphers are not checked against excluded credentials, even if the user has access to them. */ @@ -267,7 +267,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); it("should throw error if user verification fails and cipher requires reprompt", async () => { @@ -281,7 +281,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); /** Spec: If any error occurred while creating the new credential object, return an error code equivalent to "UnknownError" and terminate the operation. */ @@ -296,7 +296,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.makeCredential(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); }); @@ -434,7 +434,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error when requireUserVerification has invalid value", async () => { const result = async () => await authenticator.getAssertion(invalidParams.invalidUv, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); /** @@ -447,7 +447,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.getAssertion(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Constraint); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Constraint); }); }); @@ -512,7 +512,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error", async () => { const result = async () => await authenticator.getAssertion(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); }); @@ -600,7 +600,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.getAssertion(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); it("should throw error if user verification fails and cipher requires reprompt", async () => { @@ -612,7 +612,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.getAssertion(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); }); @@ -737,7 +737,7 @@ describe("FidoAuthenticatorService", () => { const result = async () => await authenticator.getAssertion(params, tab); - await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.Unknown); + await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); }); diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts index e009d8b18ca..bef5616ca76 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts @@ -3,8 +3,8 @@ import { Utils } from "../../../platform/misc/utils"; import { CipherService } from "../../abstractions/cipher.service"; import { Fido2AlgorithmIdentifier, - Fido2AutenticatorError, - Fido2AutenticatorErrorCode, + Fido2AuthenticatorError, + Fido2AuthenticatorErrorCode, Fido2AuthenticatorGetAssertionParams, Fido2AuthenticatorGetAssertionResult, Fido2AuthenticatorMakeCredentialResult, @@ -62,7 +62,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.warning( `[Fido2Authenticator] No compatible algorithms found, RP requested: ${requestedAlgorithms}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotSupported); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotSupported); } if ( @@ -74,7 +74,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr params.requireResidentKey )}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } if ( @@ -86,7 +86,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr params.requireUserVerification )}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } await userInterfaceSession.ensureUnlockedVault(); @@ -100,7 +100,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr `[Fido2Authenticator] Aborting due to excluded credential found in vault.` ); await userInterfaceSession.informExcludedCredential(existingCipherIds); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } let cipher: CipherView; @@ -120,7 +120,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.warning( `[Fido2Authenticator] Aborting because user confirmation was not recieved.` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } try { @@ -138,7 +138,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.warning( `[Fido2Authenticator] Aborting because user verification was unsuccessful.` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } fido2Credential = await createKeyView(params, keyPair.privateKey); @@ -150,7 +150,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.error( `[Fido2Authenticator] Aborting because of unknown error when creating credential: ${error}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } const authData = await generateAuthData({ @@ -200,7 +200,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr params.requireUserVerification )}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } let cipherOptions: CipherView[]; @@ -222,7 +222,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr `[Fido2Authenticator] Aborting because no matching credentials were found in the vault.` ); await userInterfaceSession.informCredentialNotFound(); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } const response = await userInterfaceSession.pickCredential({ @@ -237,7 +237,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.error( `[Fido2Authenticator] Aborting because the selected credential could not be found.` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } if ( @@ -247,7 +247,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.warning( `[Fido2Authenticator] Aborting because user verification was unsuccessful.` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } try { @@ -289,7 +289,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr this.logService?.error( `[Fido2Authenticator] Aborting because of unknown error when asserting credential: ${error}` ); - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } } finally { userInterfaceSession.close(); @@ -383,7 +383,7 @@ async function createKeyView( keyValue: CryptoKey ): Promise { if (keyValue.algorithm.name !== "ECDSA" && (keyValue.algorithm as any).namedCurve !== "P-256") { - throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown); + throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown); } const pkcs8Key = await crypto.subtle.exportKey("pkcs8", keyValue); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts index a8b2a071c14..3bab5da102e 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts @@ -5,8 +5,8 @@ import { AuthenticationStatus } from "../../../auth/enums/authentication-status" import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/config.service.abstraction"; import { Utils } from "../../../platform/misc/utils"; import { - Fido2AutenticatorError, - Fido2AutenticatorErrorCode, + Fido2AuthenticatorError, + Fido2AuthenticatorErrorCode, Fido2AuthenticatorGetAssertionResult, Fido2AuthenticatorMakeCredentialResult, } from "../../abstractions/fido2/fido2-authenticator.service.abstraction"; @@ -181,7 +181,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error if authenticator throws InvalidState", async () => { const params = createParams(); authenticator.makeCredential.mockRejectedValue( - new Fido2AutenticatorError(Fido2AutenticatorErrorCode.InvalidState) + new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.InvalidState) ); const result = async () => await client.createCredential(params, tab); @@ -329,7 +329,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error if authenticator throws InvalidState", async () => { const params = createParams(); authenticator.getAssertion.mockRejectedValue( - new Fido2AutenticatorError(Fido2AutenticatorErrorCode.InvalidState) + new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.InvalidState) ); const result = async () => await client.assertCredential(params, tab); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index 16049fd08a6..4c7eaf00a79 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -7,8 +7,8 @@ import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/ import { LogService } from "../../../platform/abstractions/log.service"; import { Utils } from "../../../platform/misc/utils"; import { - Fido2AutenticatorError, - Fido2AutenticatorErrorCode, + Fido2AuthenticatorError, + Fido2AuthenticatorErrorCode, Fido2AuthenticatorGetAssertionParams, Fido2AuthenticatorMakeCredentialsParams, Fido2AuthenticatorService, @@ -162,8 +162,8 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { } if ( - error instanceof Fido2AutenticatorError && - error.errorCode === Fido2AutenticatorErrorCode.InvalidState + error instanceof Fido2AuthenticatorError && + error.errorCode === Fido2AuthenticatorErrorCode.InvalidState ) { this.logService?.warning(`[Fido2Client] Unknown error: ${error}`); throw new DOMException("Unknown error occured.", "InvalidStateError"); @@ -268,8 +268,8 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { } if ( - error instanceof Fido2AutenticatorError && - error.errorCode === Fido2AutenticatorErrorCode.InvalidState + error instanceof Fido2AuthenticatorError && + error.errorCode === Fido2AuthenticatorErrorCode.InvalidState ) { this.logService?.warning(`[Fido2Client] Unknown error: ${error}`); throw new DOMException("Unknown error occured.", "InvalidStateError"); From c145763dedf40a62abe7bdab645c40ace8072401 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:23:26 -0400 Subject: [PATCH 02/79] Update bitwarden/gh-actions digest to c970b0f (#6529) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/brew-bump-cli.yml | 2 +- .github/workflows/brew-bump-desktop.yml | 2 +- .github/workflows/build-browser.yml | 4 ++-- .github/workflows/build-cli.yml | 2 +- .github/workflows/build-desktop.yml | 8 +++---- .github/workflows/build-web.yml | 6 ++--- .github/workflows/crowdin-pull.yml | 4 ++-- .github/workflows/deploy-eu-prod-web.yml | 4 ++-- .github/workflows/deploy-eu-qa-web.yml | 4 ++-- .github/workflows/deploy-non-prod-web.yml | 2 +- .github/workflows/release-browser.yml | 6 ++--- .github/workflows/release-cli.yml | 24 ++++++++++---------- .github/workflows/release-desktop-beta.yml | 8 +++---- .github/workflows/release-desktop.yml | 22 +++++++++--------- .github/workflows/release-web.yml | 12 +++++----- .github/workflows/staged-rollout-desktop.yml | 2 +- .github/workflows/version-bump.yml | 6 ++--- .github/workflows/workflow-linter.yml | 2 +- 18 files changed, 60 insertions(+), 60 deletions(-) diff --git a/.github/workflows/brew-bump-cli.yml b/.github/workflows/brew-bump-cli.yml index 8273ab00e80..4663e5079b9 100644 --- a/.github/workflows/brew-bump-cli.yml +++ b/.github/workflows/brew-bump-cli.yml @@ -23,7 +23,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "brew-bump-workflow-pat" diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml index 4032f5883a0..03a46970d28 100644 --- a/.github/workflows/brew-bump-desktop.yml +++ b/.github/workflows/brew-bump-desktop.yml @@ -23,7 +23,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "brew-bump-workflow-pat" diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index f2441c79536..becb3b9bf3b 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -375,7 +375,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -437,7 +437,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index ffffdccb8a7..978ef26ea80 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -415,7 +415,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 8b0482c3671..d55a6d13648 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -287,7 +287,7 @@ jobs: node-gyp install $(node -v) - name: Install AST - uses: bitwarden/gh-actions/install-ast@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/install-ast@c970b0fb89bd966749280e832928db62040812bf - name: Set up environmentF run: choco install checksum --no-progress @@ -312,7 +312,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "code-signing-vault-url, @@ -1204,7 +1204,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -1283,7 +1283,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index d0360b4a4ed..af591c8f3c3 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -197,7 +197,7 @@ jobs: - name: Retrieve github PAT secrets id: retrieve-secret-pat - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -273,7 +273,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -334,7 +334,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 0ff79d68296..d63682df68d 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -32,13 +32,13 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" - name: Download translations - uses: bitwarden/gh-actions/crowdin@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/crowdin@c970b0fb89bd966749280e832928db62040812bf env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/deploy-eu-prod-web.yml b/.github/workflows/deploy-eu-prod-web.yml index aeb5d2c0197..569cd155e66 100644 --- a/.github/workflows/deploy-eu-prod-web.yml +++ b/.github/workflows/deploy-eu-prod-web.yml @@ -24,13 +24,13 @@ jobs: - name: Retrieve Storage Account connection string id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: webvault-westeurope-prod secrets: "sa-bitwarden-web-vault-dev-key-temp" - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/deploy-eu-qa-web.yml b/.github/workflows/deploy-eu-qa-web.yml index 0b71fbc9981..bebf50d45e5 100644 --- a/.github/workflows/deploy-eu-qa-web.yml +++ b/.github/workflows/deploy-eu-qa-web.yml @@ -24,13 +24,13 @@ jobs: - name: Retrieve Storage Account connection string id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: webvaulteu-westeurope-qa secrets: "sa-bitwarden-web-vault-dev-key-temp" - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/deploy-non-prod-web.yml b/.github/workflows/deploy-non-prod-web.yml index 8e5b8f5c9f3..47f0e0ff0a9 100644 --- a/.github/workflows/deploy-non-prod-web.yml +++ b/.github/workflows/deploy-non-prod-web.yml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 32cf8d18546..c55aaf2401c 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -41,7 +41,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -103,7 +103,7 @@ jobs: - name: Download latest Release build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-browser.yml workflow_conclusion: success @@ -116,7 +116,7 @@ jobs: - name: Dry Run - Download latest master build artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-browser.yml workflow_conclusion: success diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 04a8a04f4f5..e74bae7f818 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -57,7 +57,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -78,7 +78,7 @@ jobs: - name: Download all Release artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli @@ -87,7 +87,7 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli @@ -150,7 +150,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "snapcraft-store-token" @@ -160,7 +160,7 @@ jobs: - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli @@ -170,7 +170,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli @@ -204,7 +204,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "cli-choco-api-key" @@ -220,7 +220,7 @@ jobs: - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli/dist @@ -230,7 +230,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli/dist @@ -263,14 +263,14 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "npm-api-key" - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli/build @@ -280,7 +280,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-cli.yml path: apps/cli/build diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index b2e568632b5..10ef295f615 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -48,7 +48,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: 'Initial Release' project-type: ts @@ -241,7 +241,7 @@ jobs: node-gyp install $(node -v) - name: Install AST - uses: bitwarden/gh-actions/install-ast@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/install-ast@c970b0fb89bd966749280e832928db62040812bf - name: Set up environment run: choco install checksum --no-progress @@ -259,7 +259,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "code-signing-vault-url, @@ -945,7 +945,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index b9ffa80e512..03a3a7c41b5 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -67,7 +67,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: ${{ inputs.release_type }} project-type: ts @@ -110,7 +110,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, @@ -123,7 +123,7 @@ jobs: - name: Download all artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success @@ -132,7 +132,7 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success @@ -185,7 +185,7 @@ jobs: --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-checksum@c970b0fb89bd966749280e832928db62040812bf with: packages_dir: "apps/desktop/artifacts" file_path: "apps/desktop/artifacts/sha256-checksums.txt" @@ -263,7 +263,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "snapcraft-store-token" @@ -277,7 +277,7 @@ jobs: - name: Download Snap artifact if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success @@ -287,7 +287,7 @@ jobs: - name: Dry Run - Download Snap artifact if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success @@ -327,7 +327,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "cli-choco-api-key" @@ -345,7 +345,7 @@ jobs: - name: Download choco artifact if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success @@ -355,7 +355,7 @@ jobs: - name: Dry Run - Download choco artifact if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-desktop.yml workflow_conclusion: success diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index cbf83dc9cdf..e440929801d 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -41,7 +41,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -130,7 +130,7 @@ jobs: - name: Retrieve bot secrets id: retrieve-bot-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: bitwarden-ci secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -144,7 +144,7 @@ jobs: - name: Download latest cloud asset if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: assets @@ -154,7 +154,7 @@ jobs: - name: Dry Run - Download latest cloud asset if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: assets @@ -227,7 +227,7 @@ jobs: - name: Download latest build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: apps/web/artifacts @@ -238,7 +238,7 @@ jobs: - name: Dry Run - Download latest build artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build-web.yml path: apps/web/artifacts diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index a3eacb868fd..7f0e428ffb8 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -26,7 +26,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 14755097b0e..ebacacecfe2 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -54,7 +54,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" @@ -125,14 +125,14 @@ jobs: - name: Bump Browser Version - Manifest if: ${{ inputs.bump_browser == true }} - uses: bitwarden/gh-actions/version-bump@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/version-bump@c970b0fb89bd966749280e832928db62040812bf with: version: ${{ inputs.version_number }} file_path: "apps/browser/src/manifest.json" - name: Bump Browser Version - Manifest v3 if: ${{ inputs.bump_browser == true }} - uses: bitwarden/gh-actions/version-bump@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/version-bump@c970b0fb89bd966749280e832928db62040812bf with: version: ${{ inputs.version_number }} file_path: "apps/browser/src/manifest.v3.json" diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index 49388c11f82..5afd077e8d2 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -8,4 +8,4 @@ on: jobs: call-workflow: - uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@c970b0fb89bd966749280e832928db62040812bf From d3cb27325698682d0002bd52ffa30600b9e629d2 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 18 Oct 2023 12:40:50 -0400 Subject: [PATCH 03/79] [PM-4141] Bugfix - Non-Premium accounts can autofill TOTP codes with the autofill keyboard shortcut (#6496) * null totp seed from retrieved login cipher for autofill if the account does not have access to premium features * update tests --- .../services/autofill.service.spec.ts | 25 ++++++++++++++++--- .../src/autofill/services/autofill.service.ts | 5 ++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index f8f12fa7ddb..544b0da1a5c 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -519,8 +519,9 @@ describe("AutofillService", () => { it("returns a TOTP value", async () => { const totpCode = "123456"; autofillOptions.cipher.login.totp = "totp"; - jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValueOnce(false); - jest.spyOn(totpService, "getCode").mockReturnValueOnce(Promise.resolve(totpCode)); + jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); + jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(false); + jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); const autofillResult = await autofillService.doAutoFill(autofillOptions); @@ -529,6 +530,18 @@ describe("AutofillService", () => { expect(autofillResult).toBe(totpCode); }); + it("does not return a TOTP value if the user does not have premium features", async () => { + autofillOptions.cipher.login.totp = "totp"; + jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(false); + jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(false); + + const autofillResult = await autofillService.doAutoFill(autofillOptions); + + expect(stateService.getDisableAutoTotpCopy).not.toHaveBeenCalled(); + expect(totpService.getCode).not.toHaveBeenCalled(); + expect(autofillResult).toBeNull(); + }); + it("returns a null value if the cipher type is not for a Login", async () => { autofillOptions.cipher.type = CipherType.Identity; autofillOptions.cipher.identity = mock(); @@ -563,11 +576,15 @@ describe("AutofillService", () => { it("returns a null value if the user has disabled `auto TOTP copy`", async () => { autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.organizationUseTotp = true; - jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValueOnce(true); - jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValueOnce(true); + jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); + jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(true); + jest.spyOn(totpService, "getCode"); const autofillResult = await autofillService.doAutoFill(autofillOptions); + expect(stateService.getCanAccessPremium).toHaveBeenCalled(); + expect(stateService.getDisableAutoTotpCopy).toHaveBeenCalled(); + expect(totpService.getCode).not.toHaveBeenCalled(); expect(autofillResult).toBeNull(); }); }); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index aca72562287..8ab20027756 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -153,6 +153,10 @@ export default class AutofillService implements AutofillServiceInterface { const canAccessPremium = await this.stateService.getCanAccessPremium(); const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain; + if (!canAccessPremium) { + options.cipher.login.totp = null; + } + let didAutofill = false; await Promise.all( options.pageDetails.map(async (pd) => { @@ -203,6 +207,7 @@ export default class AutofillService implements AutofillServiceInterface { { frameId: pd.frameId } ); + // Skip getting the TOTP code for clipboard in these cases if ( options.cipher.type !== CipherType.Login || totp !== null || From 0c782c137d3c6b4c64181d102a27d9e26bd6a690 Mon Sep 17 00:00:00 2001 From: Opeyemi Date: Wed, 18 Oct 2023 17:56:50 +0100 Subject: [PATCH 04/79] add artifact check for non prod deploy (#6624) * add artifact check for non prod deploy * UPDATE: add setup need for cfpages-deploy --- .github/workflows/deploy-non-prod-web.yml | 48 ++++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-non-prod-web.yml b/.github/workflows/deploy-non-prod-web.yml index 47f0e0ff0a9..4145cffa1b2 100644 --- a/.github/workflows/deploy-non-prod-web.yml +++ b/.github/workflows/deploy-non-prod-web.yml @@ -41,11 +41,55 @@ jobs: echo "environment-branch=cf-pages-$ENV_NAME_LOWER" >> $GITHUB_OUTPUT echo "environment-artifact=web-*-cloud-${{ inputs.environment }}.zip" >> $GITHUB_OUTPUT + artifact-check: + name: Check if Web artifact is present + runs-on: ubuntu-22.04 + needs: setup + env: + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} + steps: + - name: Download latest cloud asset + uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + id: download-artifacts + continue-on-error: true + with: + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} + + - name: Login to Azure + if: ${{ steps.download-artifacts.outcome == 'failure' }} + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets for Build trigger + if: ${{ steps.download-artifacts.outcome == 'failure' }} + id: retrieve-secret + uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 + with: + keyvault: "bitwarden-ci" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: Build server missing branch + if: ${{ steps.download-artifacts.outcome == 'failure' }} + uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be # v1.6.5 + with: + owner: bitwarden + repo: clients + github_token: ${{ steps.retrieve-secret.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + workflow_file_name: build-web.yml + ref: ${{ github.ref_name }} + wait_interval: 100 cfpages-deploy: name: Deploy Web Vault to ${{ inputs.environment }} CloudFlare Pages branch - needs: setup - runs-on: ubuntu-20.04 + needs: + - setup + - artifact-check + runs-on: ubuntu-22.04 env: _ENVIRONMENT: ${{ needs.setup.outputs.environment }} _ENVIRONMENT_URL: ${{ needs.setup.outputs.environment-url }} From eabdbe3d1911c1e69388e67aa0fcdc3c94f6da67 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 18 Oct 2023 10:18:51 -0700 Subject: [PATCH 05/79] [AC-1595] Update SSO identifier hint (#6608) --- apps/web/src/locales/en/messages.json | 7 ++++--- .../bit-web/src/app/auth/sso/sso.component.html | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 390b7f71ad7..5b4b2ac8625 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3640,7 +3640,7 @@ }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." - }, + }, "secretsCannotCreate": { "message": "Secrets cannot be created in suspended organizations. Please contact your organization owner for assistance." }, @@ -3995,8 +3995,9 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO." + "ssoIdentifierHintPartOne": { + "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" }, "unlinkSso": { "message": "Unlink SSO" diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index fa589b2438a..48567dcd505 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -30,7 +30,10 @@

{{ "singleSignOn" | i18n }}

{{ "ssoIdentifier" | i18n }} - {{ "ssoIdentifierHint" | i18n }} + + {{ "ssoIdentifierHintPartOne" | i18n }} + {{ "domainVerification" | i18n }} +
From 21b1f87724e474249d9ca78f6efadc9fc9bcfb1f Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 18 Oct 2023 14:46:42 -0400 Subject: [PATCH 06/79] PM-4376 update size for passkey popup (#6615) --- .../src/platform/popup/browser-popout-window.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/browser/src/platform/popup/browser-popout-window.service.ts b/apps/browser/src/platform/popup/browser-popout-window.service.ts index 2d661780ce0..c0ebcff6704 100644 --- a/apps/browser/src/platform/popup/browser-popout-window.service.ts +++ b/apps/browser/src/platform/popup/browser-popout-window.service.ts @@ -122,8 +122,7 @@ class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface { promptWindowPath, "fido2Popout", { - width: 200, - height: 500, + height: 450, } ); } From 5dd2e3a1e34be8c6c643aa537a4c7af95589f78e Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 18 Oct 2023 16:25:24 -0400 Subject: [PATCH 07/79] [PM-4358] Passkey can be Created but not Retrieved on eBay (#6617) * changed behaviour to require user verification when preferred use preferred when user verification is not defined * changed behaviour to require user verification when preferred use preferred when user verification is not defined --- .../vault/services/fido2/fido2-client.service.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index 4c7eaf00a79..0d113d5d458 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -363,9 +363,14 @@ function mapToMakeCredentialParams({ (params.authenticatorSelection?.residentKey === undefined && params.authenticatorSelection?.requireResidentKey === true); + const requireUserVerification = + params.authenticatorSelection?.userVerification === "required" || + params.authenticatorSelection?.userVerification === "preferred" || + params.authenticatorSelection?.userVerification === undefined; + return { requireResidentKey, - requireUserVerification: params.authenticatorSelection?.userVerification === "required", + requireUserVerification, enterpriseAttestationPossible: params.attestation === "enterprise", excludeCredentialDescriptorList, credTypesAndPubKeyAlgs, @@ -398,9 +403,14 @@ function mapToGetAssertionParams({ type: "public-key", })); + const requireUserVerification = + params.userVerification === "required" || + params.userVerification === "preferred" || + params.userVerification === undefined; + return { rpId: params.rpId, - requireUserVerification: params.userVerification === "required", + requireUserVerification, hash: clientDataHash, allowCredentialDescriptorList, extensions: {}, From fbe960e76082c4ade6f955c31234d2f63afc202e Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 18 Oct 2023 16:44:39 -0400 Subject: [PATCH 08/79] PM-4382 minor cleanup around the passkey popup (#6629) --- .../src/platform/browser/browser-api.ts | 42 +++++++++---------- .../fido2/fido2-cipher-row.component.ts | 2 +- .../fido2/fido2-use-browser-link.component.ts | 4 +- .../components/fido2/fido2.component.html | 8 ++-- .../popup/components/fido2/fido2.component.ts | 4 +- .../components/vault/add-edit.component.ts | 15 ++++--- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index e8070f0d341..dce72f5cf65 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -235,6 +235,27 @@ export class BrowserApi { } } + static messageListener$() { + return new Observable((subscriber) => { + const handler = (message: unknown) => { + subscriber.next(message); + }; + + BrowserApi.messageListener("message", handler); + + return () => { + chrome.runtime.onMessage.removeListener(handler); + + if (BrowserApi.isSafariApi) { + const index = BrowserApi.registeredMessageListeners.indexOf(handler); + if (index !== -1) { + BrowserApi.registeredMessageListeners.splice(index, 1); + } + } + }; + }); + } + static storageChangeListener( callback: Parameters[0] ) { @@ -262,27 +283,6 @@ export class BrowserApi { }; } - static messageListener$() { - return new Observable((subscriber) => { - const handler = (message: unknown) => { - subscriber.next(message); - }; - - BrowserApi.messageListener("message", handler); - - return () => { - chrome.runtime.onMessage.removeListener(handler); - - if (BrowserApi.isSafariApi) { - const index = BrowserApi.registeredMessageListeners.indexOf(handler); - if (index !== -1) { - BrowserApi.registeredMessageListeners.splice(index, 1); - } - } - }; - }); - } - static sendMessage(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); return chrome.runtime.sendMessage(message); diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts index 21ff136bf42..c07d2ef8860 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts @@ -14,7 +14,7 @@ export class Fido2CipherRowComponent { @Input() isSearching: boolean; @Input() isSelected: boolean; - selectCipher(c: CipherView) { + protected selectCipher(c: CipherView) { this.onSelected.emit(c); } } diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts index 712f728c320..aaebb225b87 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts @@ -11,9 +11,9 @@ import { templateUrl: "fido2-use-browser-link.component.html", }) export class Fido2UseBrowserLinkComponent { - fido2PopoutSessionData$ = fido2PopoutSessionData$(); + protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); - async abort() { + protected async abort() { const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId, true); return; diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.html b/apps/browser/src/vault/popup/components/fido2/fido2.component.html index 0f298b67fb6..7233fa7636a 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.html +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.html @@ -7,13 +7,13 @@ - + - + - +

{{ "passkeyAlreadyExists" | i18n }}

@@ -120,7 +120,7 @@
- +

{{ "noPasskeysFoundForThisApplication" | i18n }}

diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts index ed0ddbd1443..03ae926859b 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts @@ -217,7 +217,7 @@ export class Fido2Component implements OnInit, OnDestroy { }); } - async submit() { + protected async submit() { const data = this.message$.value; if (data?.type === "PickCredentialRequest") { const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); @@ -254,7 +254,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.loading = true; } - async saveNewLogin() { + protected async saveNewLogin() { const data = this.message$.value; if (data?.type === "ConfirmNewCredentialRequest") { let userVerified = false; diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index ded14ec92ec..971f3bee5b8 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -168,13 +168,12 @@ export class AddEditComponent extends BaseAddEditComponent { async submit(): Promise { const fido2SessionData = await firstValueFrom(this.fido2PopoutSessionData$); // Would be refactored after rework is done on the windows popout service + + const { isFido2Session, sessionId, userVerification } = fido2SessionData; if ( this.inPopout && - fido2SessionData.isFido2Session && - !(await this.handleFido2UserVerification( - fido2SessionData.sessionId, - fido2SessionData.userVerification - )) + isFido2Session && + !(await this.handleFido2UserVerification(sessionId, userVerification)) ) { return false; } @@ -184,11 +183,11 @@ export class AddEditComponent extends BaseAddEditComponent { return false; } - if (this.inPopout && fido2SessionData.isFido2Session) { + if (this.inPopout && isFido2Session) { BrowserFido2UserInterfaceSession.confirmNewCredentialResponse( - fido2SessionData.sessionId, + sessionId, this.cipher.id, - fido2SessionData.userVerification + userVerification ); return true; } From 742e6e3b950d124ee9b106e415ed7dc627679a60 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 18 Oct 2023 17:38:38 -0400 Subject: [PATCH 09/79] refactor search method in fido2 component to only show ciphers with matching uri when search text is empty (#6628) --- .../components/fido2/fido2.component.html | 2 +- .../popup/components/fido2/fido2.component.ts | 45 +++++-------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.html b/apps/browser/src/vault/popup/components/fido2/fido2.component.html index 7233fa7636a..fa49192c725 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.html +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.html @@ -20,7 +20,7 @@ placeholder="{{ 'searchVault' | i18n }}" id="search" [(ngModel)]="searchText" - (input)="search(200)" + (input)="search()" autocomplete="off" appAutofocus /> diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts index 03ae926859b..b4ad5e5a528 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts @@ -319,46 +319,23 @@ export class Fido2Component implements OnInit, OnDestroy { }); } - async loadLoginCiphers() { - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( - (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted - ); - if (!this.hasLoadedAllCiphers) { - this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText); - } - await this.search(null); - } - - async search(timeout: number = null) { - this.searchPending = false; - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - - if (timeout == null) { - this.hasSearched = this.searchService.isSearchable(this.searchText); + protected async search() { + this.hasSearched = this.searchService.isSearchable(this.searchText); + this.searchPending = true; + if (this.hasSearched) { this.displayedCiphers = await this.searchService.searchCiphers( this.searchText, null, this.ciphers ); - return; + } else { + const equivalentDomains = this.settingsService.getEquivalentDomains(this.url); + this.displayedCiphers = this.ciphers.filter((cipher) => + cipher.login.matchesUri(this.url, equivalentDomains) + ); } - this.searchPending = true; - this.searchTimeout = setTimeout(async () => { - this.hasSearched = this.searchService.isSearchable(this.searchText); - if (!this.hasLoadedAllCiphers && !this.hasSearched) { - await this.loadLoginCiphers(); - } else { - this.displayedCiphers = await this.searchService.searchCiphers( - this.searchText, - null, - this.ciphers - ); - } - this.searchPending = false; - this.selectedPasskey(this.displayedCiphers[0]); - }, timeout); + this.searchPending = false; + this.selectedPasskey(this.displayedCiphers[0]); } abort(fallback: boolean) { From d0e72f5554330cd30a048659f006c5378349a958 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 19 Oct 2023 10:03:32 +0200 Subject: [PATCH 10/79] [PM-4360] Move auth owned code into auth (#6595) --- .../{ => auth}/popup/components/set-pin.component.html | 0 .../src/{ => auth}/popup/components/set-pin.component.ts | 2 +- apps/browser/src/auth/popup/environment.component.ts | 2 +- apps/browser/src/auth/popup/register.component.ts | 2 +- apps/browser/src/auth/popup/set-password.component.ts | 2 +- apps/browser/src/background/main.background.ts | 4 ++-- apps/browser/src/popup/app.module.ts | 2 +- apps/browser/src/popup/services/services.module.ts | 2 +- apps/browser/src/popup/settings/settings.component.ts | 2 +- apps/desktop/src/app/accounts/settings.component.ts | 2 +- apps/desktop/src/app/app.module.ts | 2 +- .../src/{app => auth}/components/set-pin.component.html | 0 .../src/{app => auth}/components/set-pin.component.ts | 2 +- apps/desktop/src/auth/environment.component.ts | 2 +- apps/desktop/src/auth/register.component.ts | 2 +- apps/desktop/src/auth/set-password.component.ts | 2 +- .../app/auth/register-form/register-form.component.ts | 2 +- apps/web/src/app/auth/set-password.component.ts | 2 +- .../base-login-decryption-options.component.ts | 2 +- .../src/{ => auth}/components/environment.component.ts | 2 +- .../src/{ => auth}/components/register.component.ts | 9 +++++---- .../src/{ => auth}/components/set-password.component.ts | 2 +- .../src/{ => auth}/components/set-pin.component.ts | 2 +- libs/angular/src/services/jslib-services.module.ts | 4 ++-- .../device-trust-crypto.service.abstraction.ts | 2 +- .../auth/abstractions/devices-api.service.abstraction.ts | 2 +- .../abstractions/devices/devices.service.abstraction.ts | 0 .../abstractions/devices/responses/device.response.ts | 4 ++-- .../{ => auth}/abstractions/devices/views/device.view.ts | 4 ++-- .../device-trust-crypto.service.implementation.ts | 2 +- .../auth/services/device-trust-crypto.service.spec.ts | 2 +- .../auth/services/devices-api.service.implementation.ts | 5 +++-- .../services/devices/devices.service.implementation.ts | 4 ++-- .../devices/requests/trusted-device-keys.request.ts | 0 34 files changed, 41 insertions(+), 39 deletions(-) rename apps/browser/src/{ => auth}/popup/components/set-pin.component.html (100%) rename apps/browser/src/{ => auth}/popup/components/set-pin.component.ts (82%) rename apps/desktop/src/{app => auth}/components/set-pin.component.html (100%) rename apps/desktop/src/{app => auth}/components/set-pin.component.ts (82%) rename libs/angular/src/{ => auth}/components/environment.component.ts (97%) rename libs/angular/src/{ => auth}/components/register.component.ts (97%) rename libs/angular/src/{ => auth}/components/set-password.component.ts (99%) rename libs/angular/src/{ => auth}/components/set-pin.component.ts (97%) rename libs/common/src/{ => auth}/abstractions/devices/devices.service.abstraction.ts (100%) rename libs/common/src/{ => auth}/abstractions/devices/responses/device.response.ts (84%) rename libs/common/src/{ => auth}/abstractions/devices/views/device.view.ts (77%) rename libs/common/src/{ => auth}/services/devices/devices.service.implementation.ts (93%) rename libs/common/src/{ => auth}/services/devices/requests/trusted-device-keys.request.ts (100%) diff --git a/apps/browser/src/popup/components/set-pin.component.html b/apps/browser/src/auth/popup/components/set-pin.component.html similarity index 100% rename from apps/browser/src/popup/components/set-pin.component.html rename to apps/browser/src/auth/popup/components/set-pin.component.html diff --git a/apps/browser/src/popup/components/set-pin.component.ts b/apps/browser/src/auth/popup/components/set-pin.component.ts similarity index 82% rename from apps/browser/src/popup/components/set-pin.component.ts rename to apps/browser/src/auth/popup/components/set-pin.component.ts index 47b8fd72128..41ce33eabde 100644 --- a/apps/browser/src/popup/components/set-pin.component.ts +++ b/apps/browser/src/auth/popup/components/set-pin.component.ts @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; -import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/components/set-pin.component"; +import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component"; @Component({ templateUrl: "set-pin.component.html", diff --git a/apps/browser/src/auth/popup/environment.component.ts b/apps/browser/src/auth/popup/environment.component.ts index c70b5f597c1..a5cbfe7c34b 100644 --- a/apps/browser/src/auth/popup/environment.component.ts +++ b/apps/browser/src/auth/popup/environment.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component"; +import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/auth/components/environment.component"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/browser/src/auth/popup/register.component.ts b/apps/browser/src/auth/popup/register.component.ts index 1599af2216c..837fcb2160f 100644 --- a/apps/browser/src/auth/popup/register.component.ts +++ b/apps/browser/src/auth/popup/register.component.ts @@ -2,7 +2,7 @@ import { Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component"; +import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index 73b092f32e0..565727f76e2 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/components/set-password.component"; +import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4fcfa685270..e00beaefe4a 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1,7 +1,6 @@ import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; @@ -18,6 +17,7 @@ import { ProviderService } from "@bitwarden/common/admin-console/services/provid import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; @@ -27,6 +27,7 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@ import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; @@ -63,7 +64,6 @@ import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/we import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service"; import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; -import { DevicesServiceImplementation } from "@bitwarden/common/services/devices/devices.service.implementation"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index dd27a419a34..d452e2d512b 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -16,6 +16,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; +import { SetPinComponent } from "../auth/popup/components/set-pin.component"; import { EnvironmentComponent } from "../auth/popup/environment.component"; import { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; @@ -60,7 +61,6 @@ import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { PopOutComponent } from "./components/pop-out.component"; import { PrivateModeWarningComponent } from "./components/private-mode-warning.component"; -import { SetPinComponent } from "./components/set-pin.component"; import { UserVerificationComponent } from "./components/user-verification.component"; import { ServicesModule } from "./services/services.module"; import { AutofillComponent } from "./settings/autofill.component"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 6770e152504..6eb0e075c16 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -7,7 +7,6 @@ import { ThemingService } from "@bitwarden/angular/services/theming/theming.serv import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; @@ -27,6 +26,7 @@ import { PolicyApiService } from "@bitwarden/common/admin-console/services/polic import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index c2699ce498e..274ed871225 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -33,9 +33,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService } from "@bitwarden/components"; +import { SetPinComponent } from "../../auth/popup/components/set-pin.component"; import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { SetPinComponent } from "../components/set-pin.component"; import { PopupUtilsService } from "../services/popup-utils.service"; import { AboutComponent } from "./about.component"; diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index a2a9e71a32b..8d6ef07a515 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -19,9 +19,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService } from "@bitwarden/components"; +import { SetPinComponent } from "../../auth/components/set-pin.component"; import { flagEnabled } from "../../platform/flags"; import { ElectronStateService } from "../../platform/services/electron-state.service.abstraction"; -import { SetPinComponent } from "../components/set-pin.component"; @Component({ selector: "app-settings", templateUrl: "settings.component.html", diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 172447822c9..3436e98c998 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -10,6 +10,7 @@ import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password- import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; +import { SetPinComponent } from "../auth/components/set-pin.component"; import { DeleteAccountComponent } from "../auth/delete-account.component"; import { EnvironmentComponent } from "../auth/environment.component"; import { HintComponent } from "../auth/hint.component"; @@ -41,7 +42,6 @@ import { SettingsComponent } from "./accounts/settings.component"; import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; -import { SetPinComponent } from "./components/set-pin.component"; import { UserVerificationComponent } from "./components/user-verification.component"; import { AccountSwitcherComponent } from "./layout/account-switcher.component"; import { HeaderComponent } from "./layout/header.component"; diff --git a/apps/desktop/src/app/components/set-pin.component.html b/apps/desktop/src/auth/components/set-pin.component.html similarity index 100% rename from apps/desktop/src/app/components/set-pin.component.html rename to apps/desktop/src/auth/components/set-pin.component.html diff --git a/apps/desktop/src/app/components/set-pin.component.ts b/apps/desktop/src/auth/components/set-pin.component.ts similarity index 82% rename from apps/desktop/src/app/components/set-pin.component.ts rename to apps/desktop/src/auth/components/set-pin.component.ts index 47b8fd72128..41ce33eabde 100644 --- a/apps/desktop/src/app/components/set-pin.component.ts +++ b/apps/desktop/src/auth/components/set-pin.component.ts @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; -import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/components/set-pin.component"; +import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component"; @Component({ templateUrl: "set-pin.component.html", diff --git a/apps/desktop/src/auth/environment.component.ts b/apps/desktop/src/auth/environment.component.ts index 1f01b7445b4..9c10a6679cb 100644 --- a/apps/desktop/src/auth/environment.component.ts +++ b/apps/desktop/src/auth/environment.component.ts @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; -import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component"; +import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/auth/components/environment.component"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/desktop/src/auth/register.component.ts b/apps/desktop/src/auth/register.component.ts index db38fd4721c..f02ce3dadaa 100644 --- a/apps/desktop/src/auth/register.component.ts +++ b/apps/desktop/src/auth/register.component.ts @@ -2,7 +2,7 @@ import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component"; +import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index fedd70b3133..c2c838c8e7f 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -1,7 +1,7 @@ import { Component, NgZone, OnDestroy } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/components/set-password.component"; +import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/apps/web/src/app/auth/register-form/register-form.component.ts b/apps/web/src/app/auth/register-form/register-form.component.ts index 0a494ec333e..a63a44e4af2 100644 --- a/apps/web/src/app/auth/register-form/register-form.component.ts +++ b/apps/web/src/app/auth/register-form/register-form.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component"; +import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index 49320ced5d8..1e799737e2a 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/components/set-password.component"; +import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/angular/src/auth/components/base-login-decryption-options.component.ts b/libs/angular/src/auth/components/base-login-decryption-options.component.ts index 3350c3999c6..0203e279197 100644 --- a/libs/angular/src/auth/components/base-login-decryption-options.component.ts +++ b/libs/angular/src/auth/components/base-login-decryption-options.component.ts @@ -15,10 +15,10 @@ import { } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; diff --git a/libs/angular/src/components/environment.component.ts b/libs/angular/src/auth/components/environment.component.ts similarity index 97% rename from libs/angular/src/components/environment.component.ts rename to libs/angular/src/auth/components/environment.component.ts index 6260d34c1d1..d37c29ec3a2 100644 --- a/libs/angular/src/components/environment.component.ts +++ b/libs/angular/src/auth/components/environment.component.ts @@ -7,7 +7,7 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ModalService } from "../services/modal.service"; +import { ModalService } from "../../services/modal.service"; @Directive() export class EnvironmentComponent { diff --git a/libs/angular/src/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts similarity index 97% rename from libs/angular/src/components/register.component.ts rename to libs/angular/src/auth/components/register.component.ts index 5363be39419..0a930ac90b7 100644 --- a/libs/angular/src/components/register.component.ts +++ b/libs/angular/src/auth/components/register.component.ts @@ -21,13 +21,14 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { DialogService } from "@bitwarden/components"; -import { CaptchaProtectedComponent } from "../auth/components/captcha-protected.component"; import { AllValidationErrors, FormValidationErrorsService, -} from "../platform/abstractions/form-validation-errors.service"; -import { PasswordColorText } from "../shared/components/password-strength/password-strength.component"; -import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator"; +} from "../../platform/abstractions/form-validation-errors.service"; +import { PasswordColorText } from "../../shared/components/password-strength/password-strength.component"; +import { InputsFieldMatch } from "../../validators/inputsFieldMatch.validator"; + +import { CaptchaProtectedComponent } from "./captcha-protected.component"; @Directive() export class RegisterComponent extends CaptchaProtectedComponent implements OnInit { diff --git a/libs/angular/src/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts similarity index 99% rename from libs/angular/src/components/set-password.component.ts rename to libs/angular/src/auth/components/set-password.component.ts index 60230e7f313..7723b9f2e4c 100644 --- a/libs/angular/src/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -23,7 +23,7 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/ge import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService } from "@bitwarden/components"; -import { ChangePasswordComponent as BaseChangePasswordComponent } from "../auth/components/change-password.component"; +import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; @Directive() export class SetPasswordComponent extends BaseChangePasswordComponent { diff --git a/libs/angular/src/components/set-pin.component.ts b/libs/angular/src/auth/components/set-pin.component.ts similarity index 97% rename from libs/angular/src/components/set-pin.component.ts rename to libs/angular/src/auth/components/set-pin.component.ts index bf92417ae68..b17b7079acd 100644 --- a/libs/angular/src/components/set-pin.component.ts +++ b/libs/angular/src/auth/components/set-pin.component.ts @@ -6,7 +6,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { ModalRef } from "./modal/modal.ref"; +import { ModalRef } from "../../components/modal/modal.ref"; @Directive() export class SetPinComponent implements OnInit { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index e2019c7d33f..fbaf3411ec6 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -4,7 +4,6 @@ import { AvatarUpdateService as AccountUpdateServiceAbstraction } from "@bitward import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/abstractions/anonymousHub.service"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; @@ -43,6 +42,7 @@ import { import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; @@ -56,6 +56,7 @@ import { AccountServiceImplementation } from "@bitwarden/common/auth/services/ac import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { LoginService } from "@bitwarden/common/auth/services/login.service"; @@ -100,7 +101,6 @@ import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-u import { AnonymousHubService } from "@bitwarden/common/services/anonymousHub.service"; import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; -import { DevicesServiceImplementation } from "@bitwarden/common/services/devices/devices.service.implementation"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; diff --git a/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts b/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts index c30a567681b..d63f15a706d 100644 --- a/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts @@ -1,6 +1,6 @@ -import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { EncString } from "../../platform/models/domain/enc-string"; import { DeviceKey, UserKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; export abstract class DeviceTrustCryptoServiceAbstraction { /** diff --git a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts index 1bf0385ba1d..c27da5c4cd3 100644 --- a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts @@ -1,5 +1,5 @@ -import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { ListResponse } from "../../models/response/list.response"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { SecretVerificationRequest } from "../models/request/secret-verification.request"; import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trust.request"; import { ProtectedDeviceResponse } from "../models/response/protected-device.response"; diff --git a/libs/common/src/abstractions/devices/devices.service.abstraction.ts b/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts similarity index 100% rename from libs/common/src/abstractions/devices/devices.service.abstraction.ts rename to libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts diff --git a/libs/common/src/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts similarity index 84% rename from libs/common/src/abstractions/devices/responses/device.response.ts rename to libs/common/src/auth/abstractions/devices/responses/device.response.ts index 46874027cda..a4e40037b05 100644 --- a/libs/common/src/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -1,5 +1,5 @@ -import { DeviceType } from "../../../enums"; -import { BaseResponse } from "../../../models/response/base.response"; +import { DeviceType } from "../../../../enums"; +import { BaseResponse } from "../../../../models/response/base.response"; export class DeviceResponse extends BaseResponse { id: string; diff --git a/libs/common/src/abstractions/devices/views/device.view.ts b/libs/common/src/auth/abstractions/devices/views/device.view.ts similarity index 77% rename from libs/common/src/abstractions/devices/views/device.view.ts rename to libs/common/src/auth/abstractions/devices/views/device.view.ts index 5438aa4de1b..ce76c77a93b 100644 --- a/libs/common/src/abstractions/devices/views/device.view.ts +++ b/libs/common/src/auth/abstractions/devices/views/device.view.ts @@ -1,5 +1,5 @@ -import { DeviceType } from "../../../enums"; -import { View } from "../../../models/view/view"; +import { DeviceType } from "../../../../enums"; +import { View } from "../../../../models/view/view"; import { DeviceResponse } from "../responses/device.response"; export class DeviceView implements View { diff --git a/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts b/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts index fe45c5e208e..956a232d4ae 100644 --- a/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts @@ -1,4 +1,3 @@ -import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; @@ -14,6 +13,7 @@ import { } from "../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../types/csprng"; import { DeviceTrustCryptoServiceAbstraction } from "../abstractions/device-trust-crypto.service.abstraction"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; import { SecretVerificationRequest } from "../models/request/secret-verification.request"; import { diff --git a/libs/common/src/auth/services/device-trust-crypto.service.spec.ts b/libs/common/src/auth/services/device-trust-crypto.service.spec.ts index b56c8b922ab..66a14ac87ce 100644 --- a/libs/common/src/auth/services/device-trust-crypto.service.spec.ts +++ b/libs/common/src/auth/services/device-trust-crypto.service.spec.ts @@ -1,6 +1,5 @@ import { matches, mock } from "jest-mock-extended"; -import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceType } from "../../enums"; import { EncryptionType } from "../../enums/encryption-type.enum"; import { AppIdService } from "../../platform/abstractions/app-id.service"; @@ -17,6 +16,7 @@ import { UserKey, } from "../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../types/csprng"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trust.request"; import { ProtectedDeviceResponse } from "../models/response/protected-device.response"; diff --git a/libs/common/src/auth/services/devices-api.service.implementation.ts b/libs/common/src/auth/services/devices-api.service.implementation.ts index e149a79ea2f..2a4331bc5c0 100644 --- a/libs/common/src/auth/services/devices-api.service.implementation.ts +++ b/libs/common/src/auth/services/devices-api.service.implementation.ts @@ -1,13 +1,14 @@ import { ApiService } from "../../abstractions/api.service"; -import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { ListResponse } from "../../models/response/list.response"; import { Utils } from "../../platform/misc/utils"; -import { TrustedDeviceKeysRequest } from "../../services/devices/requests/trusted-device-keys.request"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; import { SecretVerificationRequest } from "../models/request/secret-verification.request"; import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trust.request"; import { ProtectedDeviceResponse } from "../models/response/protected-device.response"; +import { TrustedDeviceKeysRequest } from "./devices/requests/trusted-device-keys.request"; + export class DevicesApiServiceImplementation implements DevicesApiServiceAbstraction { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts similarity index 93% rename from libs/common/src/services/devices/devices.service.implementation.ts rename to libs/common/src/auth/services/devices/devices.service.implementation.ts index fe6e2a37d2a..c83a096df48 100644 --- a/libs/common/src/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,10 +1,10 @@ import { Observable, defer, map } from "rxjs"; +import { ListResponse } from "../../../models/response/list.response"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceView } from "../../abstractions/devices/views/device.view"; -import { DevicesApiServiceAbstraction } from "../../auth/abstractions/devices-api.service.abstraction"; -import { ListResponse } from "../../models/response/list.response"; +import { DevicesApiServiceAbstraction } from "../../abstractions/devices-api.service.abstraction"; /** * @class DevicesServiceImplementation diff --git a/libs/common/src/services/devices/requests/trusted-device-keys.request.ts b/libs/common/src/auth/services/devices/requests/trusted-device-keys.request.ts similarity index 100% rename from libs/common/src/services/devices/requests/trusted-device-keys.request.ts rename to libs/common/src/auth/services/devices/requests/trusted-device-keys.request.ts From 9e290a3fed3f7d247374138245259d7b34c4d0cd Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:17:23 +0200 Subject: [PATCH 11/79] [PM-4222] Make importer UI reusable (#6504) * Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * Lazy-load the ImportWebComponent via both routes * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith --------- Co-authored-by: Daniel James Smith Co-authored-by: William Martin --- .../browser/src/background/main.background.ts | 2 +- .../src/popup/services/services.module.ts | 2 +- .../import-api-service.factory.ts | 2 +- .../import-service.factory.ts | 2 +- apps/browser/tsconfig.json | 3 +- apps/cli/src/bw.ts | 2 +- apps/cli/src/tools/import.command.ts | 2 +- apps/cli/tsconfig.json | 2 +- apps/desktop/tsconfig.json | 3 +- .../organization-settings-routing.module.ts | 11 +- .../tools/import/org-import-routing.module.ts | 25 ---- .../tools/import/org-import.component.ts | 99 ------------- .../tools/import/org-import.module.ts | 44 ------ apps/web/src/app/oss-routing.module.ts | 6 +- .../dialog/file-password-prompt.component.ts | 20 --- .../app/tools/import/import-routing.module.ts | 17 --- .../tools/import/import-web.component.html | 18 +++ .../app/tools/import/import-web.component.ts | 51 +++++++ .../web/src/app/tools/import/import.module.ts | 54 ------- apps/web/src/scss/pages.scss | 2 +- apps/web/tsconfig.json | 3 +- .../src/services/jslib-services.module.ts | 2 +- libs/components/src/async-actions/index.ts | 1 + libs/components/src/table/table.component.css | 4 + libs/components/src/table/table.component.ts | 2 + libs/components/src/tw-theme.css | 1 + .../file-password-prompt.component.html | 4 +- .../dialog/file-password-prompt.component.ts | 43 ++++++ .../dialog/import-error-dialog.component.html | 0 .../dialog/import-error-dialog.component.ts | 7 +- .../import-success-dialog.component.html | 0 .../dialog/import-success-dialog.component.ts | 9 +- .../importer/src/components}/dialog/index.ts | 0 .../src/components}/import.component.html | 20 +-- .../src/components}/import.component.ts | 135 +++++++++++++++--- libs/importer/src/components/index.ts | 3 + libs/importer/src/index.ts | 10 +- libs/importer/src/models/index.ts | 2 + libs/importer/src/services/index.ts | 5 + libs/shared/tsconfig.libs.json | 3 +- tsconfig.eslint.json | 3 +- tsconfig.json | 3 +- 42 files changed, 299 insertions(+), 328 deletions(-) delete mode 100644 apps/web/src/app/admin-console/organizations/tools/import/org-import-routing.module.ts delete mode 100644 apps/web/src/app/admin-console/organizations/tools/import/org-import.component.ts delete mode 100644 apps/web/src/app/admin-console/organizations/tools/import/org-import.module.ts delete mode 100644 apps/web/src/app/tools/import/dialog/file-password-prompt.component.ts delete mode 100644 apps/web/src/app/tools/import/import-routing.module.ts create mode 100644 apps/web/src/app/tools/import/import-web.component.html create mode 100644 apps/web/src/app/tools/import/import-web.component.ts delete mode 100644 apps/web/src/app/tools/import/import.module.ts create mode 100644 libs/components/src/table/table.component.css rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/file-password-prompt.component.html (90%) create mode 100644 libs/importer/src/components/dialog/file-password-prompt.component.ts rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/import-error-dialog.component.html (100%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/import-error-dialog.component.ts (72%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/import-success-dialog.component.html (100%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/import-success-dialog.component.ts (84%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/dialog/index.ts (100%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/import.component.html (97%) rename {apps/web/src/app/tools/import => libs/importer/src/components}/import.component.ts (78%) create mode 100644 libs/importer/src/components/index.ts create mode 100644 libs/importer/src/models/index.ts create mode 100644 libs/importer/src/services/index.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index e00beaefe4a..9d4cba04e7c 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -113,7 +113,7 @@ import { ImportApiService, ImportServiceAbstraction, ImportService, -} from "@bitwarden/importer"; +} from "@bitwarden/importer/core"; import { BrowserOrganizationService } from "../admin-console/services/browser-organization.service"; import { BrowserPolicyService } from "../admin-console/services/browser-policy.service"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 6eb0e075c16..2622b8ef13b 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -84,7 +84,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { DialogService } from "@bitwarden/components"; import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export"; -import { ImportServiceAbstraction } from "@bitwarden/importer"; +import { ImportServiceAbstraction } from "@bitwarden/importer/core"; import { BrowserOrganizationService } from "../../admin-console/services/browser-organization.service"; import { BrowserPolicyService } from "../../admin-console/services/browser-policy.service"; diff --git a/apps/browser/src/tools/background/service_factories/import-api-service.factory.ts b/apps/browser/src/tools/background/service_factories/import-api-service.factory.ts index 4344647bbe8..00954a0dc60 100644 --- a/apps/browser/src/tools/background/service_factories/import-api-service.factory.ts +++ b/apps/browser/src/tools/background/service_factories/import-api-service.factory.ts @@ -1,4 +1,4 @@ -import { ImportApiService, ImportApiServiceAbstraction } from "@bitwarden/importer"; +import { ImportApiService, ImportApiServiceAbstraction } from "@bitwarden/importer/core"; import { ApiServiceInitOptions, diff --git a/apps/browser/src/tools/background/service_factories/import-service.factory.ts b/apps/browser/src/tools/background/service_factories/import-service.factory.ts index 3dc9bbd4f0a..7f5328f4d07 100644 --- a/apps/browser/src/tools/background/service_factories/import-service.factory.ts +++ b/apps/browser/src/tools/background/service_factories/import-service.factory.ts @@ -1,4 +1,4 @@ -import { ImportService, ImportServiceAbstraction } from "@bitwarden/importer"; +import { ImportService, ImportServiceAbstraction } from "@bitwarden/importer/core"; import { cryptoServiceFactory, diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 357be6c5281..3ad2be7c02c 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -15,7 +15,8 @@ "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/exporter/*": ["../../libs/exporter/src/*"], - "@bitwarden/importer": ["../../libs/importer/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/vault": ["../../libs/vault/src"] }, "useDefineForClassFields": false diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index ca501690193..ffaec215e26 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -74,7 +74,7 @@ import { ImportApiServiceAbstraction, ImportService, ImportServiceAbstraction, -} from "@bitwarden/importer"; +} from "@bitwarden/importer/core"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; import { CliConfigService } from "./platform/services/cli-config.service"; diff --git a/apps/cli/src/tools/import.command.ts b/apps/cli/src/tools/import.command.ts index e3f24b960f8..1be562239f5 100644 --- a/apps/cli/src/tools/import.command.ts +++ b/apps/cli/src/tools/import.command.ts @@ -3,7 +3,7 @@ import * as inquirer from "inquirer"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer"; +import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer/core"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 420496ad93c..395d91564a0 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -14,7 +14,7 @@ "paths": { "@bitwarden/common/spec": ["../../libs/common/spec"], "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/importer": ["../../libs/importer/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/exporter/*": ["../../libs/exporter/src/*"], "@bitwarden/node/*": ["../../libs/node/src/*"] } diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 67bfba64426..b479905f897 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -15,7 +15,8 @@ "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/exporter/*": ["../../libs/exporter/src/*"], - "@bitwarden/importer": ["../../libs/importer/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/vault": ["../../libs/vault/src"] }, "useDefineForClassFields": false diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index 1606d86497c..d853b942cec 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -48,8 +48,15 @@ const routes: Routes = [ children: [ { path: "import", - loadChildren: () => - import("../tools/import/org-import.module").then((m) => m.OrganizationImportModule), + loadComponent: () => + import("../../../tools/import/import-web.component").then( + (mod) => mod.ImportWebComponent + ), + canActivate: [OrganizationPermissionsGuard], + data: { + titleId: "importData", + organizationPermissions: (org: Organization) => org.canAccessImportExport, + }, }, { path: "export", diff --git a/apps/web/src/app/admin-console/organizations/tools/import/org-import-routing.module.ts b/apps/web/src/app/admin-console/organizations/tools/import/org-import-routing.module.ts deleted file mode 100644 index 9b4b4b5c787..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/import/org-import-routing.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; - -import { OrganizationPermissionsGuard } from "../../guards/org-permissions.guard"; - -import { OrganizationImportComponent } from "./org-import.component"; - -const routes: Routes = [ - { - path: "", - component: OrganizationImportComponent, - canActivate: [OrganizationPermissionsGuard], - data: { - titleId: "importData", - organizationPermissions: (org: Organization) => org.canAccessImportExport, - }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class OrganizationImportRoutingModule {} diff --git a/apps/web/src/app/admin-console/organizations/tools/import/org-import.component.ts b/apps/web/src/app/admin-console/organizations/tools/import/org-import.component.ts deleted file mode 100644 index b514f2bcf38..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/import/org-import.component.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Component } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { switchMap, takeUntil } from "rxjs/operators"; - -import { - canAccessVaultTab, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService } from "@bitwarden/components"; -import { ImportServiceAbstraction } from "@bitwarden/importer"; - -import { ImportComponent } from "../../../../tools/import/import.component"; - -@Component({ - selector: "app-org-import", - templateUrl: "../../../../tools/import/import.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class OrganizationImportComponent extends ImportComponent { - organization: Organization; - - protected get importBlockedByPolicy(): boolean { - return false; - } - - constructor( - i18nService: I18nService, - importService: ImportServiceAbstraction, - router: Router, - private route: ActivatedRoute, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - organizationService: OrganizationService, - logService: LogService, - syncService: SyncService, - dialogService: DialogService, - folderService: FolderService, - collectionService: CollectionService, - formBuilder: FormBuilder - ) { - super( - i18nService, - importService, - router, - platformUtilsService, - policyService, - logService, - syncService, - dialogService, - folderService, - collectionService, - organizationService, - formBuilder - ); - } - - ngOnInit() { - this.route.params - .pipe( - switchMap((params) => this.organizationService.get$(params.organizationId)), - takeUntil(this.destroy$) - ) - .subscribe((organization) => { - this.organizationId = organization.id; - this.organization = organization; - }); - super.ngOnInit(); - } - - protected async onSuccessfulImport(): Promise { - if (canAccessVaultTab(this.organization)) { - await this.router.navigate(["organizations", this.organizationId, "vault"]); - } else { - this.fileSelected = null; - } - } - - protected async performImport() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "warning" }, - content: { key: "importWarning", placeholders: [this.organization.name] }, - type: "warning", - }); - - if (!confirmed) { - return; - } - await super.performImport(); - } -} diff --git a/apps/web/src/app/admin-console/organizations/tools/import/org-import.module.ts b/apps/web/src/app/admin-console/organizations/tools/import/org-import.module.ts deleted file mode 100644 index 66a22158e20..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/import/org-import.module.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { - ImportService, - ImportServiceAbstraction, - ImportApiService, - ImportApiServiceAbstraction, -} from "@bitwarden/importer"; - -import { LooseComponentsModule, SharedModule } from "../../../../shared"; - -import { OrganizationImportRoutingModule } from "./org-import-routing.module"; -import { OrganizationImportComponent } from "./org-import.component"; - -@NgModule({ - imports: [SharedModule, LooseComponentsModule, OrganizationImportRoutingModule], - declarations: [OrganizationImportComponent], - providers: [ - { - provide: ImportApiServiceAbstraction, - useClass: ImportApiService, - deps: [ApiService], - }, - { - provide: ImportServiceAbstraction, - useClass: ImportService, - deps: [ - CipherService, - FolderService, - ImportApiServiceAbstraction, - I18nService, - CollectionService, - CryptoService, - ], - }, - ], -}) -export class OrganizationImportModule {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index d8a57cd3fb3..eee22c0d467 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -255,7 +255,11 @@ const routes: Routes = [ { path: "", pathMatch: "full", redirectTo: "generator" }, { path: "import", - loadChildren: () => import("./tools/import/import.module").then((m) => m.ImportModule), + loadComponent: () => + import("./tools/import/import-web.component").then((mod) => mod.ImportWebComponent), + data: { + titleId: "importData", + }, }, { path: "export", diff --git a/apps/web/src/app/tools/import/dialog/file-password-prompt.component.ts b/apps/web/src/app/tools/import/dialog/file-password-prompt.component.ts deleted file mode 100644 index d0cbfe20fd6..00000000000 --- a/apps/web/src/app/tools/import/dialog/file-password-prompt.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DialogRef } from "@angular/cdk/dialog"; -import { Component } from "@angular/core"; -import { FormControl, Validators } from "@angular/forms"; - -@Component({ - templateUrl: "file-password-prompt.component.html", -}) -export class FilePasswordPromptComponent { - filePassword = new FormControl("", Validators.required); - - constructor(public dialogRef: DialogRef) {} - - submit() { - this.filePassword.markAsTouched(); - if (!this.filePassword.valid) { - return; - } - this.dialogRef.close(this.filePassword.value); - } -} diff --git a/apps/web/src/app/tools/import/import-routing.module.ts b/apps/web/src/app/tools/import/import-routing.module.ts deleted file mode 100644 index 82d0a733846..00000000000 --- a/apps/web/src/app/tools/import/import-routing.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { ImportComponent } from "./import.component"; - -const routes: Routes = [ - { - path: "", - component: ImportComponent, - data: { titleId: "importData" }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class ImportRoutingModule {} diff --git a/apps/web/src/app/tools/import/import-web.component.html b/apps/web/src/app/tools/import/import-web.component.html new file mode 100644 index 00000000000..1d655db3243 --- /dev/null +++ b/apps/web/src/app/tools/import/import-web.component.html @@ -0,0 +1,18 @@ +

{{ "importData" | i18n }}

+ + diff --git a/apps/web/src/app/tools/import/import-web.component.ts b/apps/web/src/app/tools/import/import-web.component.ts new file mode 100644 index 00000000000..952a717cd95 --- /dev/null +++ b/apps/web/src/app/tools/import/import-web.component.ts @@ -0,0 +1,51 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { + OrganizationService, + canAccessVaultTab, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { ImportComponent } from "@bitwarden/importer/ui"; + +import { SharedModule } from "../../shared"; + +@Component({ + templateUrl: "import-web.component.html", + standalone: true, + imports: [SharedModule, ImportComponent], +}) +export class ImportWebComponent implements OnInit { + protected routeOrgId: string = null; + protected loading = false; + protected disabled = false; + + constructor( + private route: ActivatedRoute, + private organizationService: OrganizationService, + private router: Router + ) {} + + ngOnInit(): void { + this.routeOrgId = this.route.snapshot.paramMap.get("organizationId"); + } + + /** + * Callback that is called after a successful import. + */ + protected async onSuccessfulImport(organizationId: string): Promise { + if (!organizationId) { + await this.router.navigate(["vault"]); + return; + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (organization == null) { + return; + } + + if (canAccessVaultTab(organization)) { + await this.router.navigate(["organizations", organizationId, "vault"]); + } + } +} diff --git a/apps/web/src/app/tools/import/import.module.ts b/apps/web/src/app/tools/import/import.module.ts deleted file mode 100644 index 34f71cfab7f..00000000000 --- a/apps/web/src/app/tools/import/import.module.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { - ImportService, - ImportServiceAbstraction, - ImportApiService, - ImportApiServiceAbstraction, -} from "@bitwarden/importer"; - -import { LooseComponentsModule, SharedModule } from "../../shared"; - -import { - ImportErrorDialogComponent, - ImportSuccessDialogComponent, - FilePasswordPromptComponent, -} from "./dialog"; -import { ImportRoutingModule } from "./import-routing.module"; -import { ImportComponent } from "./import.component"; - -@NgModule({ - imports: [SharedModule, LooseComponentsModule, ImportRoutingModule], - declarations: [ - ImportComponent, - FilePasswordPromptComponent, - ImportErrorDialogComponent, - ImportSuccessDialogComponent, - ], - providers: [ - { - provide: ImportApiServiceAbstraction, - useClass: ImportApiService, - deps: [ApiService], - }, - { - provide: ImportServiceAbstraction, - useClass: ImportService, - deps: [ - CipherService, - FolderService, - ImportApiServiceAbstraction, - I18nService, - CollectionService, - CryptoService, - ], - }, - ], -}) -export class ImportModule {} diff --git a/apps/web/src/scss/pages.scss b/apps/web/src/scss/pages.scss index 4dbee2ac507..684d45a1a66 100644 --- a/apps/web/src/scss/pages.scss +++ b/apps/web/src/scss/pages.scss @@ -32,7 +32,7 @@ app-password-generator-history { } } -app-import { +tools-import { textarea { height: 150px; } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 67d24511584..a1ea71f4bb9 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -10,7 +10,8 @@ "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/exporter/*": ["../../libs/exporter/src/*"], - "@bitwarden/importer": ["../../libs/importer/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["src/*"] } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index fbaf3411ec6..e52e9c394e4 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -154,7 +154,7 @@ import { ImportApiServiceAbstraction, ImportService, ImportServiceAbstraction, -} from "@bitwarden/importer"; +} from "@bitwarden/importer/core"; import { PasswordRepromptService } from "@bitwarden/vault"; import { AuthGuard } from "../auth/guards/auth.guard"; diff --git a/libs/components/src/async-actions/index.ts b/libs/components/src/async-actions/index.ts index 6515ffc47ca..05f49902a78 100644 --- a/libs/components/src/async-actions/index.ts +++ b/libs/components/src/async-actions/index.ts @@ -1,3 +1,4 @@ export * from "./async-actions.module"; export * from "./bit-action.directive"; export * from "./form-button.directive"; +export * from "./bit-submit.directive"; diff --git a/libs/components/src/table/table.component.css b/libs/components/src/table/table.component.css new file mode 100644 index 00000000000..e764a9130ea --- /dev/null +++ b/libs/components/src/table/table.component.css @@ -0,0 +1,4 @@ +th { + text-align: inherit; + text-align: -webkit-match-parent; +} diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index 9f36d0a70fc..b4d6d1931d1 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -39,6 +39,8 @@ export class TableComponent implements OnDestroy, AfterContentChecked { "tw-w-full", "tw-leading-normal", "tw-text-main", + "tw-border-collapse", + "tw-text-start", this.layout === "auto" ? "tw-table-auto" : "tw-table-fixed", ]; } diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index 1ff2064fdbd..14b5fcb4338 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -159,6 +159,7 @@ } @import "./search/search.component.css"; +@import "./table/table.component.css"; /** * tw-break-words does not work with table cells: diff --git a/apps/web/src/app/tools/import/dialog/file-password-prompt.component.html b/libs/importer/src/components/dialog/file-password-prompt.component.html similarity index 90% rename from apps/web/src/app/tools/import/dialog/file-password-prompt.component.html rename to libs/importer/src/components/dialog/file-password-prompt.component.html index 6c849da69df..823d6ebec1b 100644 --- a/apps/web/src/app/tools/import/dialog/file-password-prompt.component.html +++ b/libs/importer/src/components/dialog/file-password-prompt.component.html @@ -1,4 +1,4 @@ -
+ {{ "confirmVaultImport" | i18n }} @@ -12,7 +12,7 @@ bitInput type="password" name="filePassword" - [formControl]="filePassword" + formControlName="filePassword" appAutofocus appInputVerbatim /> diff --git a/libs/importer/src/components/dialog/file-password-prompt.component.ts b/libs/importer/src/components/dialog/file-password-prompt.component.ts new file mode 100644 index 00000000000..864fdafab08 --- /dev/null +++ b/libs/importer/src/components/dialog/file-password-prompt.component.ts @@ -0,0 +1,43 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + AsyncActionsModule, + ButtonModule, + DialogModule, + FormFieldModule, + IconButtonModule, +} from "@bitwarden/components"; + +@Component({ + templateUrl: "file-password-prompt.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + DialogModule, + FormFieldModule, + AsyncActionsModule, + ButtonModule, + IconButtonModule, + ReactiveFormsModule, + ], +}) +export class FilePasswordPromptComponent { + formGroup = this.formBuilder.group({ + filePassword: ["", Validators.required], + }); + + constructor(public dialogRef: DialogRef, protected formBuilder: FormBuilder) {} + + submit = () => { + this.formGroup.markAsTouched(); + if (!this.formGroup.valid) { + return; + } + this.dialogRef.close(this.formGroup.value.filePassword); + }; +} diff --git a/apps/web/src/app/tools/import/dialog/import-error-dialog.component.html b/libs/importer/src/components/dialog/import-error-dialog.component.html similarity index 100% rename from apps/web/src/app/tools/import/dialog/import-error-dialog.component.html rename to libs/importer/src/components/dialog/import-error-dialog.component.html diff --git a/apps/web/src/app/tools/import/dialog/import-error-dialog.component.ts b/libs/importer/src/components/dialog/import-error-dialog.component.ts similarity index 72% rename from apps/web/src/app/tools/import/dialog/import-error-dialog.component.ts rename to libs/importer/src/components/dialog/import-error-dialog.component.ts index abb68cf53b1..4d766e3619d 100644 --- a/apps/web/src/app/tools/import/dialog/import-error-dialog.component.ts +++ b/libs/importer/src/components/dialog/import-error-dialog.component.ts @@ -1,7 +1,9 @@ import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; import { Component, Inject, OnInit } from "@angular/core"; -import { TableDataSource } from "@bitwarden/components"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ButtonModule, DialogModule, TableDataSource, TableModule } from "@bitwarden/components"; export interface ErrorListItem { type: string; @@ -9,8 +11,9 @@ export interface ErrorListItem { } @Component({ - selector: "app-import-error-dialog", templateUrl: "./import-error-dialog.component.html", + standalone: true, + imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule], }) export class ImportErrorDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/tools/import/dialog/import-success-dialog.component.html b/libs/importer/src/components/dialog/import-success-dialog.component.html similarity index 100% rename from apps/web/src/app/tools/import/dialog/import-success-dialog.component.html rename to libs/importer/src/components/dialog/import-success-dialog.component.html diff --git a/apps/web/src/app/tools/import/dialog/import-success-dialog.component.ts b/libs/importer/src/components/dialog/import-success-dialog.component.ts similarity index 84% rename from apps/web/src/app/tools/import/dialog/import-success-dialog.component.ts rename to libs/importer/src/components/dialog/import-success-dialog.component.ts index 215784cb6f4..4d10002da48 100644 --- a/apps/web/src/app/tools/import/dialog/import-success-dialog.component.ts +++ b/libs/importer/src/components/dialog/import-success-dialog.component.ts @@ -1,9 +1,12 @@ import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; import { Component, Inject, OnInit } from "@angular/core"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; -import { TableDataSource } from "@bitwarden/components"; -import { ImportResult } from "@bitwarden/importer"; +import { ButtonModule, DialogModule, TableDataSource, TableModule } from "@bitwarden/components"; + +import { ImportResult } from "../../models"; export interface ResultList { icon: string; @@ -13,6 +16,8 @@ export interface ResultList { @Component({ templateUrl: "./import-success-dialog.component.html", + standalone: true, + imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule], }) export class ImportSuccessDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/tools/import/dialog/index.ts b/libs/importer/src/components/dialog/index.ts similarity index 100% rename from apps/web/src/app/tools/import/dialog/index.ts rename to libs/importer/src/components/dialog/index.ts diff --git a/apps/web/src/app/tools/import/import.component.html b/libs/importer/src/components/import.component.html similarity index 97% rename from apps/web/src/app/tools/import/import.component.html rename to libs/importer/src/components/import.component.html index c9e3285c291..83e119fcc57 100644 --- a/apps/web/src/app/tools/import/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -1,9 +1,7 @@ -

{{ "importData" | i18n }}

- {{ "personalOwnershipPolicyInEffectImports" | i18n }} - + {{ "importDestination" | i18n }} @@ -349,12 +347,7 @@

{{ "importData" | i18n }}

{{ "selectImportFile" | i18n }}
- {{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }} @@ -380,13 +373,4 @@

{{ "importData" | i18n }}

formControlName="fileContents" > - diff --git a/apps/web/src/app/tools/import/import.component.ts b/libs/importer/src/components/import.component.ts similarity index 78% rename from apps/web/src/app/tools/import/import.component.ts rename to libs/importer/src/components/import.component.ts index 1f71b2fd775..3a111720ab3 100644 --- a/apps/web/src/app/tools/import/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -1,10 +1,20 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import * as JSZip from "jszip"; import { concat, Observable, Subject, lastValueFrom, combineLatest } from "rxjs"; import { map, takeUntil } from "rxjs/operators"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { canAccessImportExport, OrganizationService, @@ -12,22 +22,35 @@ import { import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; import { - ImportOption, - ImportResult, + AsyncActionsModule, + BitSubmitDirective, + ButtonModule, + CalloutModule, + DialogService, + FormFieldModule, + IconButtonModule, + SelectModule, +} from "@bitwarden/components"; + +import { ImportOption, ImportResult, ImportType } from "../models"; +import { + ImportApiService, + ImportApiServiceAbstraction, + ImportService, ImportServiceAbstraction, - ImportType, -} from "@bitwarden/importer"; +} from "../services"; import { FilePasswordPromptComponent, @@ -36,8 +59,39 @@ import { } from "./dialog"; @Component({ - selector: "app-import", + selector: "tools-import", templateUrl: "import.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + FormFieldModule, + AsyncActionsModule, + ButtonModule, + IconButtonModule, + SelectModule, + CalloutModule, + ReactiveFormsModule, + ], + providers: [ + { + provide: ImportApiServiceAbstraction, + useClass: ImportApiService, + deps: [ApiService], + }, + { + provide: ImportServiceAbstraction, + useClass: ImportService, + deps: [ + CipherService, + FolderService, + ImportApiServiceAbstraction, + I18nService, + CollectionService, + CryptoService, + ], + }, + ], }) export class ImportComponent implements OnInit, OnDestroy { featuredImportOptions: ImportOption[]; @@ -49,7 +103,24 @@ export class ImportComponent implements OnInit, OnDestroy { collections$: Observable; organizations$: Observable; - protected organizationId: string = null; + private _organizationId: string; + + get organizationId(): string { + return this._organizationId; + } + + @Input() set organizationId(value: string) { + this._organizationId = value; + this.organizationService + .get$(this._organizationId) + .pipe(takeUntil(this.destroy$)) + .subscribe((organization) => { + this._organizationId = organization?.id; + this.organization = organization; + }); + } + + protected organization: Organization; protected destroy$ = new Subject(); private _importBlockedByPolicy = false; @@ -68,10 +139,31 @@ export class ImportComponent implements OnInit, OnDestroy { file: [], }); + @ViewChild(BitSubmitDirective) + private bitSubmit: BitSubmitDirective; + + @Output() + formLoading = new EventEmitter(); + + @Output() + formDisabled = new EventEmitter(); + + @Output() + onSuccessfulImport = new EventEmitter(); + + ngAfterViewInit(): void { + this.bitSubmit.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => { + this.formLoading.emit(loading); + }); + + this.bitSubmit.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { + this.formDisabled.emit(disabled); + }); + } + constructor( protected i18nService: I18nService, protected importService: ImportServiceAbstraction, - protected router: Router, protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private logService: LogService, @@ -87,13 +179,6 @@ export class ImportComponent implements OnInit, OnDestroy { return this._importBlockedByPolicy; } - /** - * Callback that is called after a successful import. - */ - protected async onSuccessfulImport(): Promise { - await this.router.navigate(["vault"]); - } - ngOnInit() { this.setImportOptions(); @@ -167,6 +252,18 @@ export class ImportComponent implements OnInit, OnDestroy { }; protected async performImport() { + if (this.organization) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "warning" }, + content: { key: "importWarning", placeholders: [this.organization.name] }, + type: "warning", + }); + + if (!confirmed) { + return; + } + } + if (this.importBlockedByPolicy) { this.platformUtilsService.showToast( "error", @@ -246,7 +343,7 @@ export class ImportComponent implements OnInit, OnDestroy { }); this.syncService.fullSync(true); - await this.onSuccessfulImport(); + this.onSuccessfulImport.emit(this._organizationId); } catch (e) { this.dialogService.open(ImportErrorDialogComponent, { data: e, diff --git a/libs/importer/src/components/index.ts b/libs/importer/src/components/index.ts new file mode 100644 index 00000000000..a2e59f1714f --- /dev/null +++ b/libs/importer/src/components/index.ts @@ -0,0 +1,3 @@ +export * from "./dialog"; + +export { ImportComponent } from "./import.component"; diff --git a/libs/importer/src/index.ts b/libs/importer/src/index.ts index 4586407659e..4c6c4131ba4 100644 --- a/libs/importer/src/index.ts +++ b/libs/importer/src/index.ts @@ -1,11 +1,5 @@ -export { ImportType, ImportOption } from "./models/import-options"; +export * from "./models"; -export { ImportResult } from "./models/import-result"; - -export { ImportApiServiceAbstraction } from "./services/import-api.service.abstraction"; -export { ImportApiService } from "./services/import-api.service"; - -export { ImportServiceAbstraction } from "./services/import.service.abstraction"; -export { ImportService } from "./services/import.service"; +export * from "./services"; export { Importer } from "./importers/importer"; diff --git a/libs/importer/src/models/index.ts b/libs/importer/src/models/index.ts new file mode 100644 index 00000000000..95eb910957c --- /dev/null +++ b/libs/importer/src/models/index.ts @@ -0,0 +1,2 @@ +export { ImportType, ImportOption } from "./import-options"; +export { ImportResult } from "./import-result"; diff --git a/libs/importer/src/services/index.ts b/libs/importer/src/services/index.ts new file mode 100644 index 00000000000..7b1244867f5 --- /dev/null +++ b/libs/importer/src/services/index.ts @@ -0,0 +1,5 @@ +export { ImportApiServiceAbstraction } from "./import-api.service.abstraction"; +export { ImportApiService } from "./import-api.service"; + +export { ImportServiceAbstraction } from "./import.service.abstraction"; +export { ImportService } from "./import.service"; diff --git a/libs/shared/tsconfig.libs.json b/libs/shared/tsconfig.libs.json index 85d55542707..1addd88d4cc 100644 --- a/libs/shared/tsconfig.libs.json +++ b/libs/shared/tsconfig.libs.json @@ -7,7 +7,8 @@ "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], "@bitwarden/exporter/*": ["../exporter/src/*"], - "@bitwarden/importer": ["../importer/src"], + "@bitwarden/importer/core": ["../importer/src"], + "@bitwarden/importer/ui": ["../importer/src/components"], "@bitwarden/node/*": ["../node/src/*"], "@bitwarden/vault": ["../vault/src"] } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index d96a144ebcb..0ccac14430c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -20,7 +20,8 @@ "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/exporter/*": ["./libs/exporter/src/*"], - "@bitwarden/importer": ["./libs/importer/src"], + "@bitwarden/importer/core": ["./libs/importer/src"], + "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/vault": ["./libs/vault/src"] }, diff --git a/tsconfig.json b/tsconfig.json index d6e4db99f57..f6f6b4ee30f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/exporter/*": ["./libs/exporter/src/*"], - "@bitwarden/importer": ["./libs/importer/src"], + "@bitwarden/importer/core": ["./libs/importer/src"], + "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/web-vault/*": ["./apps/web/src/*"], "@bitwarden/vault": ["./libs/vault/src"] From e357819251c241661e92f99af54126e0885405b3 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:00:06 +0200 Subject: [PATCH 12/79] [PM-4197] Enable importing on deskop (#6502) * Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * use formLoading & formDisabled in desktop * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * use formLoading & formDisabled in desktop * Add missing message for importBlockedByPolicy callout * Remove commented code for submit button * Implement onSuccessfulImport to close dialog on success * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * update selectors * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * Lazy-load the ImportWebComponent via both routes * Fix import path for ImportComponent * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Add missing messages for file-password-prompt * Add missing messages for import-error-dialog * Add missing message for import-success-dialog * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith * Use large dialogSize * PM-4398 - Add missing importWarning --------- Co-authored-by: Daniel James Smith Co-authored-by: William Martin --- apps/desktop/src/app/app.component.ts | 4 + .../import/import-desktop.component.html | 26 ++++ .../tools/import/import-desktop.component.ts | 33 ++++++ apps/desktop/src/locales/en/messages.json | 111 ++++++++++++++++++ apps/desktop/src/main/menu/menu.file.ts | 10 ++ 5 files changed, 184 insertions(+) create mode 100644 apps/desktop/src/app/tools/import/import-desktop.component.html create mode 100644 apps/desktop/src/app/tools/import/import-desktop.component.ts diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 65ac83b59fd..b98dd0a8398 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -55,6 +55,7 @@ import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.compo import { SettingsComponent } from "./accounts/settings.component"; import { ExportComponent } from "./tools/export/export.component"; import { GeneratorComponent } from "./tools/generator.component"; +import { ImportDesktopComponent } from "./tools/import/import-desktop.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; const BroadcasterSubscriptionId = "AppComponent"; @@ -328,6 +329,9 @@ export class AppComponent implements OnInit, OnDestroy { } this.messagingService.send("scheduleNextSync"); break; + case "importVault": + await this.dialogService.open(ImportDesktopComponent); + break; case "exportVault": await this.openExportVault(); break; diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html new file mode 100644 index 00000000000..74d4098255b --- /dev/null +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -0,0 +1,26 @@ + + {{ "importData" | i18n }} + + + + + + + + diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.ts b/apps/desktop/src/app/tools/import/import-desktop.component.ts new file mode 100644 index 00000000000..62fc007731d --- /dev/null +++ b/apps/desktop/src/app/tools/import/import-desktop.component.ts @@ -0,0 +1,33 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import { ImportComponent } from "@bitwarden/importer/ui"; + +@Component({ + templateUrl: "import-desktop.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + ], +}) +export class ImportDesktopComponent { + protected disabled = false; + protected loading = false; + + constructor(public dialogRef: DialogRef) {} + + /** + * Callback that is called after a successful import. + */ + protected async onSuccessfulImport(organizationId: string): Promise { + this.dialogRef.close(); + } +} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 52fdbf1b560..537be07da97 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1662,6 +1662,9 @@ "personalOwnershipPolicyInEffect": { "message": "An organization policy is affecting your ownership options." }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2427,5 +2430,113 @@ }, "aliasDomain": { "message": "Alias domain" + }, + "importData": { + "message": "Import data", + "description": "Used for the desktop menu item and the header of the import dialog" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "resolveTheErrorsBelowAndTryAgain": { + "message": "Resolve the errors below and try again." + }, + "description": { + "message": "Description" + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importSuccessNumberOfItems": { + "message": "A total of $AMOUNT$ items were imported.", + "placeholders": { + "amount": { + "content": "$1", + "example": "2" + } + } + }, + "total": { + "message": "Total" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "importDestination": { + "message": "Import destination" + }, + "learnAboutImportOptions": { + "message": "Learn about your import options" + }, + "selectImportFolder": { + "message": "Select a folder" + }, + "selectImportCollection": { + "message": "Select a collection" + }, + "importTargetHint": { + "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", + "placeholders": { + "destination": { + "content": "$1", + "example": "folder or collection" + } + } + }, + "importUnassignedItemsError": { + "message": "File contains unassigned items." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "confirmFilePassword": { + "message": "Confirm file password" } } diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index 173b6066aba..0782039d7c2 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -24,6 +24,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { this.addNewFolder, this.separator, this.syncVault, + this.importVault, this.exportVault, ]; @@ -123,6 +124,15 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { }; } + private get importVault(): MenuItemConstructorOptions { + return { + id: "importVault", + label: this.localize("importData"), + click: () => this.sendMessage("importVault"), + enabled: !this._isLocked, + }; + } + private get exportVault(): MenuItemConstructorOptions { return { id: "exportVault", From 790d666929f67968fb16a443146b37ffe13298da Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 19 Oct 2023 20:21:53 +0200 Subject: [PATCH 13/79] [PM-4401] Fix zone.js patch compatibility issues in safari (#6633) * [PM-4401] fix: zone.js patch compatibility issues in safari * Update apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts Co-authored-by: Oscar Hinton --------- Co-authored-by: Oscar Hinton Co-authored-by: SmithThe4th --- .../polyfills/zone-patch-chrome-runtime.ts | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts b/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts index fa731840f8b..055518fc751 100644 --- a/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts +++ b/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts @@ -2,35 +2,43 @@ * Monkey patch `chrome.runtime.onMessage` event listeners to run in the Angular zone. */ Zone.__load_patch("ChromeRuntimeOnMessage", (global: any, Zone: ZoneType, api: _ZonePrivate) => { - const onMessage = global.chrome.runtime.onMessage; if (typeof global?.chrome?.runtime?.onMessage === "undefined") { return; } + const onMessage = global.chrome.runtime.onMessage; // eslint-disable-next-line @typescript-eslint/ban-types - api.patchMethod(onMessage, "addListener", (delegate: Function) => (self: any, args: any[]) => { - const callback = args.length > 0 ? args[0] : null; - if (typeof callback === "function") { - const wrapperedCallback = Zone.current.wrap(callback, "ChromeRuntimeOnMessage"); - callback[api.symbol("chromeRuntimeOnMessageCallback")] = wrapperedCallback; - return delegate.call(self, wrapperedCallback); - } else { - return delegate.apply(self, args); - } + const nativeAddListener = onMessage.addListener as Function; + api.ObjectDefineProperty(chrome.runtime.onMessage, "addListener", { + value: function (...args: any[]) { + const callback = args.length > 0 ? args[0] : null; + if (typeof callback === "function") { + const wrapperedCallback = Zone.current.wrap(callback, "ChromeRuntimeOnMessage"); + callback[api.symbol("chromeRuntimeOnMessageCallback")] = wrapperedCallback; + return nativeAddListener.call(onMessage, wrapperedCallback); + } else { + return nativeAddListener.apply(onMessage, args); + } + }, + writable: false, }); // eslint-disable-next-line @typescript-eslint/ban-types - api.patchMethod(onMessage, "removeListener", (delegate: Function) => (self: any, args: any[]) => { - const callback = args.length > 0 ? args[0] : null; - if (typeof callback === "function") { - const wrapperedCallback = callback[api.symbol("chromeRuntimeOnMessageCallback")]; - if (wrapperedCallback) { - return delegate.call(self, wrapperedCallback); + const nativeRemoveListener = onMessage.removeListener as Function; + api.ObjectDefineProperty(chrome.runtime.onMessage, "removeListener", { + value: function (...args: any[]) { + const callback = args.length > 0 ? args[0] : null; + if (typeof callback === "function") { + const wrapperedCallback = callback[api.symbol("chromeRuntimeOnMessageCallback")]; + if (wrapperedCallback) { + return nativeRemoveListener.call(onMessage, wrapperedCallback); + } else { + return nativeRemoveListener.apply(onMessage, args); + } } else { - return delegate.apply(self, args); + return nativeRemoveListener.apply(onMessage, args); } - } else { - return delegate.apply(self, args); - } + }, + writable: false, }); }); From 13df63fbac4612f8f8b1a4a6011e4360fefb0957 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:06:01 +0200 Subject: [PATCH 14/79] [PM-4195] Lastpass lib cleanup (#6636) * Casing fixes from the original port of the code * Add static createClientInfo and export * Add way to transform retrieve accounts into csv format Create ExportAccount model accountsToExportedCsvString can transform and export csv * Make calls needed for UI class async/awaitable * Add helpers for SSO on the UserTypeContext * Add additional error handling case * Fixes for SSO login --------- Co-authored-by: Daniel James Smith --- .../src/importers/lastpass/access/index.ts | 1 + .../lastpass/access/models/client-info.ts | 6 ++ .../access/models/exported-account.ts | 23 +++++++ .../importers/lastpass/access/models/index.ts | 1 + .../access/models/user-type-context.ts | 34 ++++++---- .../lastpass/access/services/client.ts | 20 +++--- .../lastpass/access/services/rest-client.ts | 6 +- .../src/importers/lastpass/access/ui/ui.ts | 12 ++-- .../src/importers/lastpass/access/vault.ts | 62 ++++++++++++------- 9 files changed, 116 insertions(+), 49 deletions(-) create mode 100644 libs/importer/src/importers/lastpass/access/models/exported-account.ts diff --git a/libs/importer/src/importers/lastpass/access/index.ts b/libs/importer/src/importers/lastpass/access/index.ts index a124a44b315..1ec8fe0df11 100644 --- a/libs/importer/src/importers/lastpass/access/index.ts +++ b/libs/importer/src/importers/lastpass/access/index.ts @@ -1 +1,2 @@ +export { ClientInfo } from "./models"; export { Vault } from "./vault"; diff --git a/libs/importer/src/importers/lastpass/access/models/client-info.ts b/libs/importer/src/importers/lastpass/access/models/client-info.ts index 275cdc00d3f..1f87512780c 100644 --- a/libs/importer/src/importers/lastpass/access/models/client-info.ts +++ b/libs/importer/src/importers/lastpass/access/models/client-info.ts @@ -1,7 +1,13 @@ +import { Utils } from "@bitwarden/common/platform/misc/utils"; + import { Platform } from "../enums"; export class ClientInfo { platform: Platform; id: string; description: string; + + static createClientInfo(): ClientInfo { + return { platform: Platform.Desktop, id: Utils.newGuid(), description: "Importer" }; + } } diff --git a/libs/importer/src/importers/lastpass/access/models/exported-account.ts b/libs/importer/src/importers/lastpass/access/models/exported-account.ts new file mode 100644 index 00000000000..3c42bbffc02 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/models/exported-account.ts @@ -0,0 +1,23 @@ +import { Account } from "./account"; + +export class ExportedAccount { + url: string; + username: string; + password: string; + totp: string; + extra: string; + name: string; + grouping: string; + fav: number; + + constructor(account: Account) { + this.url = account.url; + this.username = account.username; + this.password = account.password; + this.totp = account.totp; + this.extra = account.notes; + this.name = account.name; + this.grouping = account.path === "(none)" ? null : account.path; + this.fav = account.isFavorite ? 1 : 0; + } +} diff --git a/libs/importer/src/importers/lastpass/access/models/index.ts b/libs/importer/src/importers/lastpass/access/models/index.ts index a0c6121a354..9a3c5693ce5 100644 --- a/libs/importer/src/importers/lastpass/access/models/index.ts +++ b/libs/importer/src/importers/lastpass/access/models/index.ts @@ -1,6 +1,7 @@ export { Account } from "./account"; export { Chunk } from "./chunk"; export { ClientInfo } from "./client-info"; +export { ExportedAccount } from "./exported-account"; export { FederatedUserContext } from "./federated-user-context"; export { OobResult } from "./oob-result"; export { OtpResult } from "./otp-result"; diff --git a/libs/importer/src/importers/lastpass/access/models/user-type-context.ts b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts index 9d849281c2d..a4e3c8668e5 100644 --- a/libs/importer/src/importers/lastpass/access/models/user-type-context.ts +++ b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts @@ -2,24 +2,36 @@ import { IdpProvider, LastpassLoginType } from "../enums"; export class UserTypeContext { type: LastpassLoginType; - IdentityProviderGUID: string; - IdentityProviderURL: string; - OpenIDConnectAuthority: string; - OpenIDConnectClientId: string; - CompanyId: number; - Provider: IdpProvider; - PkceEnabled: boolean; - IsPasswordlessEnabled: boolean; + identityProviderGUID: string; + identityProviderURL: string; + openIDConnectAuthority: string; + openIDConnectClientId: string; + companyId: number; + provider: IdpProvider; + pkceEnabled: boolean; + isPasswordlessEnabled: boolean; isFederated(): boolean { return ( this.type === LastpassLoginType.Federated && - this.hasValue(this.IdentityProviderURL) && - this.hasValue(this.OpenIDConnectAuthority) && - this.hasValue(this.OpenIDConnectClientId) + this.hasValue(this.identityProviderURL) && + this.hasValue(this.openIDConnectAuthority) && + this.hasValue(this.openIDConnectClientId) ); } + get oidcScope(): string { + let scope = "openid profile email"; + if (this.provider === IdpProvider.PingOne) { + scope += " lastpass"; + } + return scope; + } + + get openIDConnectAuthorityBase(): string { + return this.openIDConnectAuthority.replace("/.well-known/openid-configuration", ""); + } + private hasValue(str: string) { return str != null && str.trim() !== ""; } diff --git a/libs/importer/src/importers/lastpass/access/services/client.ts b/libs/importer/src/importers/lastpass/access/services/client.ts index 2d8b503f01d..b185ada8881 100644 --- a/libs/importer/src/importers/lastpass/access/services/client.ts +++ b/libs/importer/src/importers/lastpass/access/services/client.ts @@ -229,13 +229,13 @@ export class Client { let passcode: OtpResult = null; switch (method) { case OtpMethod.GoogleAuth: - passcode = ui.provideGoogleAuthPasscode(); + passcode = await ui.provideGoogleAuthPasscode(); break; case OtpMethod.MicrosoftAuth: - passcode = ui.provideMicrosoftAuthPasscode(); + passcode = await ui.provideMicrosoftAuthPasscode(); break; case OtpMethod.Yubikey: - passcode = ui.provideYubikeyPasscode(); + passcode = await ui.provideYubikeyPasscode(); break; default: throw new Error("Invalid OTP method"); @@ -273,7 +273,7 @@ export class Client { ui: Ui, rest: RestClient ): Promise { - const answer = this.approveOob(username, parameters, ui, rest); + const answer = await this.approveOob(username, parameters, ui, rest); if (answer == OobResult.cancel) { throw new Error("Out of band step is canceled by the user"); } @@ -318,7 +318,12 @@ export class Client { return session; } - private approveOob(username: string, parameters: Map, ui: Ui, rest: RestClient) { + private async approveOob( + username: string, + parameters: Map, + ui: Ui, + rest: RestClient + ): Promise { const method = parameters.get("outofbandtype"); if (method == null) { throw new Error("Out of band method is not specified"); @@ -335,12 +340,12 @@ export class Client { } } - private approveDuo( + private async approveDuo( username: string, parameters: Map, ui: Ui, rest: RestClient - ): OobResult { + ): Promise { return parameters.get("preferduowebsdk") == "1" ? this.approveDuoWebSdk(username, parameters, ui, rest) : ui.approveDuo(); @@ -525,6 +530,7 @@ export class Client { switch (cause.value) { case "unknownemail": return "Invalid username"; + case "password_invalid": case "unknownpassword": return "Invalid password"; case "googleauthfailed": diff --git a/libs/importer/src/importers/lastpass/access/services/rest-client.ts b/libs/importer/src/importers/lastpass/access/services/rest-client.ts index b26109d8e87..ce5fede33cb 100644 --- a/libs/importer/src/importers/lastpass/access/services/rest-client.ts +++ b/libs/importer/src/importers/lastpass/access/services/rest-client.ts @@ -43,9 +43,6 @@ export class RestClient { ): Promise { const setBody = (requestInit: RequestInit, headerMap: Map) => { if (body != null) { - if (headerMap == null) { - headerMap = new Map(); - } headerMap.set("Content-Type", "application/json; charset=utf-8"); requestInit.body = JSON.stringify(body); } @@ -63,6 +60,9 @@ export class RestClient { method: "POST", credentials: "include", }; + if (headers == null) { + headers = new Map(); + } setBody(requestInit, headers); this.setHeaders(requestInit, headers, cookies); const request = new Request(this.baseUrl + "/" + endpoint, requestInit); diff --git a/libs/importer/src/importers/lastpass/access/ui/ui.ts b/libs/importer/src/importers/lastpass/access/ui/ui.ts index 2338e8a291e..b1640d325f1 100644 --- a/libs/importer/src/importers/lastpass/access/ui/ui.ts +++ b/libs/importer/src/importers/lastpass/access/ui/ui.ts @@ -4,9 +4,9 @@ import { DuoUi } from "./duo-ui"; export abstract class Ui extends DuoUi { // To cancel return OtpResult.Cancel, otherwise only valid data is expected. - provideGoogleAuthPasscode: () => OtpResult; - provideMicrosoftAuthPasscode: () => OtpResult; - provideYubikeyPasscode: () => OtpResult; + provideGoogleAuthPasscode: () => Promise; + provideMicrosoftAuthPasscode: () => Promise; + provideYubikeyPasscode: () => Promise; /* The UI implementations should provide the following possibilities for the user: @@ -23,7 +23,7 @@ export abstract class Ui extends DuoUi { passcode instead of performing an action in the app. In this case the UI should return OobResult.continueWithPasscode(passcode, rememberMe). */ - approveLastPassAuth: () => OobResult; - approveDuo: () => OobResult; - approveSalesforceAuth: () => OobResult; + approveLastPassAuth: () => Promise; + approveDuo: () => Promise; + approveSalesforceAuth: () => Promise; } diff --git a/libs/importer/src/importers/lastpass/access/vault.ts b/libs/importer/src/importers/lastpass/access/vault.ts index a461239eea8..fc38fab8714 100644 --- a/libs/importer/src/importers/lastpass/access/vault.ts +++ b/libs/importer/src/importers/lastpass/access/vault.ts @@ -1,3 +1,5 @@ +import * as papa from "papaparse"; + import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -7,6 +9,7 @@ import { IdpProvider } from "./enums"; import { Account, ClientInfo, + ExportedAccount, FederatedUserContext, ParserOptions, UserTypeContext, @@ -68,20 +71,35 @@ export class Vault { if (response.status === HttpStatusCode.Ok) { const json = await response.json(); this.userType = new UserTypeContext(); - this.userType.CompanyId = json.CompanyId; - this.userType.IdentityProviderGUID = json.IdentityProviderGUID; - this.userType.IdentityProviderURL = json.IdentityProviderURL; - this.userType.IsPasswordlessEnabled = json.IsPasswordlessEnabled; - this.userType.OpenIDConnectAuthority = json.OpenIDConnectAuthority; - this.userType.OpenIDConnectClientId = json.OpenIDConnectClientId; - this.userType.PkceEnabled = json.PkceEnabled; - this.userType.Provider = json.Provider; + this.userType.companyId = json.CompanyId; + this.userType.identityProviderGUID = json.IdentityProviderGUID; + this.userType.identityProviderURL = json.IdentityProviderURL; + this.userType.isPasswordlessEnabled = json.IsPasswordlessEnabled; + this.userType.openIDConnectAuthority = json.OpenIDConnectAuthority; + this.userType.openIDConnectClientId = json.OpenIDConnectClientId; + this.userType.pkceEnabled = json.PkceEnabled; + this.userType.provider = json.Provider; this.userType.type = json.type; return; } throw new Error("Cannot determine LastPass user type."); } + accountsToExportedCsvString(skipShared = false): string { + if (this.accounts == null) { + throw new Error("Vault has not opened any accounts."); + } + + const exportedAccounts = this.accounts + .filter((a) => !a.isShared || (a.isShared && !skipShared)) + .map((a) => new ExportedAccount(a)); + + if (exportedAccounts.length === 0) { + throw new Error("No accounts to transform"); + } + return papa.unparse(exportedAccounts); + } + private async getK1(federatedUser: FederatedUserContext): Promise { if (this.userType == null) { throw new Error("User type is not set."); @@ -96,18 +114,18 @@ export class Vault { } let k1: Uint8Array = null; - if (federatedUser.idpUserInfo?.LastPassK1 !== null) { + if (federatedUser.idpUserInfo?.LastPassK1 != null) { return Utils.fromByteStringToArray(federatedUser.idpUserInfo.LastPassK1); - } else if (this.userType.Provider === IdpProvider.Azure) { + } else if (this.userType.provider === IdpProvider.Azure) { k1 = await this.getK1Azure(federatedUser); - } else if (this.userType.Provider === IdpProvider.Google) { + } else if (this.userType.provider === IdpProvider.Google) { k1 = await this.getK1Google(federatedUser); } else { - const b64Encoded = this.userType.Provider === IdpProvider.PingOne; - k1 = this.getK1FromAccessToken(federatedUser, b64Encoded); + const b64Encoded = this.userType.provider === IdpProvider.PingOne; + k1 = await this.getK1FromAccessToken(federatedUser, b64Encoded); } - if (k1 !== null) { + if (k1 != null) { return k1; } @@ -125,7 +143,7 @@ export class Vault { if (response.status === HttpStatusCode.Ok) { const json = await response.json(); const k1 = json?.extensions?.LastPassK1 as string; - if (k1 !== null) { + if (k1 != null) { return Utils.fromB64ToArray(k1); } } @@ -149,7 +167,7 @@ export class Vault { if (response.status === HttpStatusCode.Ok) { const json = await response.json(); const files = json?.files as any[]; - if (files !== null && files.length > 0 && files[0].id != null && files[0].name === "k1.lp") { + if (files != null && files.length > 0 && files[0].id != null && files[0].name === "k1.lp") { // Open the k1.lp file rest.baseUrl = "https://www.googleapis.com"; const response = await rest.get( @@ -165,10 +183,10 @@ export class Vault { return null; } - private getK1FromAccessToken(federatedUser: FederatedUserContext, b64: boolean) { - const decodedAccessToken = this.tokenService.decodeToken(federatedUser.accessToken); + private async getK1FromAccessToken(federatedUser: FederatedUserContext, b64: boolean) { + const decodedAccessToken = await this.tokenService.decodeToken(federatedUser.accessToken); const k1 = decodedAccessToken?.LastPassK1 as string; - if (k1 !== null) { + if (k1 != null) { return b64 ? Utils.fromB64ToArray(k1) : Utils.fromByteStringToArray(k1); } return null; @@ -184,15 +202,15 @@ export class Vault { } const rest = new RestClient(); - rest.baseUrl = this.userType.IdentityProviderURL; + rest.baseUrl = this.userType.identityProviderURL; const response = await rest.postJson("federatedlogin/api/v1/getkey", { - company_id: this.userType.CompanyId, + company_id: this.userType.companyId, id_token: federatedUser.idToken, }); if (response.status === HttpStatusCode.Ok) { const json = await response.json(); const k2 = json?.k2 as string; - if (k2 !== null) { + if (k2 != null) { return Utils.fromB64ToArray(k2); } } From cdcd1809f0aaa2390c4a3dcd0f49776b3415c16b Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 19 Oct 2023 15:41:01 -0400 Subject: [PATCH 15/79] Expand account service (#6622) * Define account service observable responsibilities * Establish account service observables and update methods * Update Account Service observables from state service This is a temporary stop-gap to avoid needing to reroute all account activity and status changes through the account service. That can be done as part of the breakup of state service. * Add matchers for Observable emissions * Fix null active account * Test account service * Transition account status to account info * Remove unused matchers * Remove duplicate class * Replay active account for late subscriptions * Add factories for background services * Fix state service for web * Allow for optional messaging This is a temporary hack until the flow of account status can be reversed from state -> account to account -> state. The foreground account service will still logout, it's just the background one cannot send messages * Fix add account logic * Do not throw on recoverable errors It's possible that duplicate entries exist in `activeAccounts` exist in the wild. If we throw on adding a duplicate account this will cause applications to be unusable until duplicates are removed it is not necessary to throw since this is recoverable. with some potential loss in current account status * Add documentation to abstraction * Update libs/common/spec/utils.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix justin's comment :fist-shake: --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- .../account-service.factory.ts | 38 ++++ .../browser/src/background/main.background.ts | 7 +- .../state-service.factory.ts | 8 +- .../services/browser-state.service.spec.ts | 4 + .../services/browser-state.service.ts | 3 + .../src/popup/services/services.module.ts | 15 +- apps/cli/src/bw.ts | 8 +- .../src/app/services/services.module.ts | 2 + apps/desktop/src/main.ts | 2 + apps/web/src/app/core/state/state.service.ts | 3 + .../src/services/jslib-services.module.ts | 1 + libs/common/spec/utils.ts | 38 ++++ .../src/auth/abstractions/account.service.ts | 48 ++++- .../src/auth/services/account.service.spec.ts | 181 ++++++++++++++++++ .../src/auth/services/account.service.ts | 81 +++++++- .../src/platform/services/state.service.ts | 30 ++- libs/common/src/types/guid.d.ts | 5 + 17 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 apps/browser/src/auth/background/service-factories/account-service.factory.ts create mode 100644 libs/common/src/auth/services/account.service.spec.ts create mode 100644 libs/common/src/types/guid.d.ts diff --git a/apps/browser/src/auth/background/service-factories/account-service.factory.ts b/apps/browser/src/auth/background/service-factories/account-service.factory.ts new file mode 100644 index 00000000000..759ff8efdd5 --- /dev/null +++ b/apps/browser/src/auth/background/service-factories/account-service.factory.ts @@ -0,0 +1,38 @@ +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; + +import { + FactoryOptions, + CachedServices, + factory, +} from "../../../platform/background/service-factories/factory-options"; +import { + LogServiceInitOptions, + logServiceFactory, +} from "../../../platform/background/service-factories/log-service.factory"; +import { + MessagingServiceInitOptions, + messagingServiceFactory, +} from "../../../platform/background/service-factories/messaging-service.factory"; + +type AccountServiceFactoryOptions = FactoryOptions; + +export type AccountServiceInitOptions = AccountServiceFactoryOptions & + MessagingServiceInitOptions & + LogServiceInitOptions; + +export function accountServiceFactory( + cache: { accountService?: AccountService } & CachedServices, + opts: AccountServiceInitOptions +): Promise { + return factory( + cache, + "accountService", + opts, + async () => + new AccountServiceImplementation( + await messagingServiceFactory(cache, opts), + await logServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 9d4cba04e7c..5c47c1aaf9e 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -14,6 +14,7 @@ import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitw import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; +import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; @@ -24,6 +25,7 @@ import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/ import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; @@ -225,6 +227,7 @@ export default class MainBackground { authRequestCryptoService: AuthRequestCryptoServiceAbstraction; popupUtilsService: PopupUtilsService; browserPopoutWindowService: BrowserPopoutWindowService; + accountService: AccountServiceAbstraction; // Passed to the popup for Safari to workaround issues with theming, downloading, etc. backgroundWindow = window; @@ -279,12 +282,14 @@ export default class MainBackground { new KeyGenerationService(this.cryptoFunctionService) ) : new MemoryStorageService(); + this.accountService = new AccountServiceImplementation(this.messagingService, this.logService); this.stateService = new BrowserStateService( this.storageService, this.secureStorageService, this.memoryStorageService, this.logService, - new StateFactory(GlobalState, Account) + new StateFactory(GlobalState, Account), + this.accountService ); this.platformUtilsService = new BrowserPlatformUtilsService( this.messagingService, diff --git a/apps/browser/src/platform/background/service-factories/state-service.factory.ts b/apps/browser/src/platform/background/service-factories/state-service.factory.ts index 7d3aaf9b6f3..31a0316c09a 100644 --- a/apps/browser/src/platform/background/service-factories/state-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/state-service.factory.ts @@ -1,6 +1,10 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; +import { + accountServiceFactory, + AccountServiceInitOptions, +} from "../../../auth/background/service-factories/account-service.factory"; import { Account } from "../../../models/account"; import { BrowserStateService } from "../../services/browser-state.service"; @@ -26,7 +30,8 @@ export type StateServiceInitOptions = StateServiceFactoryOptions & DiskStorageServiceInitOptions & SecureStorageServiceInitOptions & MemoryStorageServiceInitOptions & - LogServiceInitOptions; + LogServiceInitOptions & + AccountServiceInitOptions; export async function stateServiceFactory( cache: { stateService?: BrowserStateService } & CachedServices, @@ -43,6 +48,7 @@ export async function stateServiceFactory( await memoryStorageServiceFactory(cache, opts), await logServiceFactory(cache, opts), opts.stateServiceOptions.stateFactory, + await accountServiceFactory(cache, opts), opts.stateServiceOptions.useAccountCache ) ); diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index 0712416172c..c63aae74036 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -1,5 +1,6 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractMemoryStorageService, @@ -27,6 +28,7 @@ describe("Browser State Service", () => { let logService: MockProxy; let stateFactory: MockProxy>; let useAccountCache: boolean; + let accountService: MockProxy; let state: State; const userId = "userId"; @@ -38,6 +40,7 @@ describe("Browser State Service", () => { diskStorageService = mock(); logService = mock(); stateFactory = mock(); + accountService = mock(); // turn off account cache for tests useAccountCache = false; @@ -62,6 +65,7 @@ describe("Browser State Service", () => { memoryStorageService, logService, stateFactory, + accountService, useAccountCache ); }); diff --git a/apps/browser/src/platform/services/browser-state.service.ts b/apps/browser/src/platform/services/browser-state.service.ts index ec6851beb8f..ae5abb8a897 100644 --- a/apps/browser/src/platform/services/browser-state.service.ts +++ b/apps/browser/src/platform/services/browser-state.service.ts @@ -1,5 +1,6 @@ import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractStorageService, @@ -42,6 +43,7 @@ export class BrowserStateService memoryStorageService: AbstractMemoryStorageService, logService: LogService, stateFactory: StateFactory, + accountService: AccountService, useAccountCache = true ) { super( @@ -50,6 +52,7 @@ export class BrowserStateService memoryStorageService, logService, stateFactory, + accountService, useAccountCache ); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 2622b8ef13b..06235809458 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -23,6 +23,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; +import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; @@ -453,17 +454,25 @@ function getBgService(service: keyof MainBackground) { storageService: AbstractStorageService, secureStorageService: AbstractStorageService, memoryStorageService: AbstractMemoryStorageService, - logService: LogServiceAbstraction + logService: LogServiceAbstraction, + accountService: AccountServiceAbstraction ) => { return new BrowserStateService( storageService, secureStorageService, memoryStorageService, logService, - new StateFactory(GlobalState, Account) + new StateFactory(GlobalState, Account), + accountService ); }, - deps: [AbstractStorageService, SECURE_STORAGE, MEMORY_STORAGE, LogServiceAbstraction], + deps: [ + AbstractStorageService, + SECURE_STORAGE, + MEMORY_STORAGE, + LogServiceAbstraction, + AccountServiceAbstraction, + ], }, { provide: UsernameGenerationServiceAbstraction, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index ffaec215e26..b63dda690f7 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -12,9 +12,11 @@ import { OrganizationService } from "@bitwarden/common/admin-console/services/or import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; +import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; @@ -152,6 +154,7 @@ export class Main { authRequestCryptoService: AuthRequestCryptoServiceAbstraction; configApiService: ConfigApiServiceAbstraction; configService: CliConfigService; + accountService: AccountService; constructor() { let p = null; @@ -191,12 +194,15 @@ export class Main { this.memoryStorageService = new MemoryStorageService(); + this.accountService = new AccountServiceImplementation(null, this.logService); + this.stateService = new StateService( this.storageService, this.secureStorageService, this.memoryStorageService, this.logService, - new StateFactory(GlobalState, Account) + new StateFactory(GlobalState, Account), + this.accountService ); this.cryptoService = new CryptoService( diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index f4841073c9c..c586d8677c3 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -11,6 +11,7 @@ import { import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; import { PolicyService as PolicyServiceAbstraction } 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 { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/services/login.service"; @@ -120,6 +121,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); MEMORY_STORAGE, LogService, STATE_FACTORY, + AccountServiceAbstraction, STATE_SERVICE_USE_CACHE, ], }, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 1eb229281c8..1c4f415e1c8 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -2,6 +2,7 @@ import * as path from "path"; import { app } from "electron"; +import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; @@ -93,6 +94,7 @@ export class Main { this.memoryStorageService, this.logService, new StateFactory(GlobalState, Account), + new AccountServiceImplementation(null, this.logService), // will not broadcast logouts. This is a hack until we can remove messaging dependency false // Do not use disk caching because this will get out of sync with the renderer service ); diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index c95077bfbcc..4848ad4fb78 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -6,6 +6,7 @@ import { STATE_FACTORY, STATE_SERVICE_USE_CACHE, } from "@bitwarden/angular/services/injection-tokens"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractMemoryStorageService, @@ -30,6 +31,7 @@ export class StateService extends BaseStateService { @Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService, logService: LogService, @Inject(STATE_FACTORY) stateFactory: StateFactory, + accountService: AccountService, @Inject(STATE_SERVICE_USE_CACHE) useAccountCache = true ) { super( @@ -38,6 +40,7 @@ export class StateService extends BaseStateService { memoryStorageService, logService, stateFactory, + accountService, useAccountCache ); } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index e52e9c394e4..060593d4a53 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -489,6 +489,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; MEMORY_STORAGE, LogService, STATE_FACTORY, + AccountServiceAbstraction, STATE_SERVICE_USE_CACHE, ], }, diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index 3cab011c6b0..8d6f8920318 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -1,4 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { Observable } from "rxjs"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -40,3 +41,40 @@ export function makeStaticByteArray(length: number, start = 0) { * Use to mock a return value of a static fromJSON method. */ export const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; + +/** + * Tracks the emissions of the given observable. + * + * Call this function before you expect any emissions and then use code that will cause the observable to emit values, + * then assert after all expected emissions have occurred. + * @param observable + * @returns An array that will be populated with all emissions of the observable. + */ +export function trackEmissions(observable: Observable): T[] { + const emissions: T[] = []; + observable.subscribe((value) => { + switch (value) { + case undefined: + case null: + emissions.push(value); + return; + default: + // process by type + break; + } + + switch (typeof value) { + case "string": + case "number": + case "boolean": + emissions.push(value); + break; + case "object": + emissions.push({ ...value }); + break; + default: + emissions.push(JSON.parse(JSON.stringify(value))); + } + }); + return emissions; +} diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index 26c260eb6d5..30fe32e2597 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -1,4 +1,50 @@ -export abstract class AccountService {} +import { Observable } from "rxjs"; + +import { UserId } from "../../types/guid"; +import { AuthenticationStatus } from "../enums/authentication-status"; + +export type AccountInfo = { + status: AuthenticationStatus; + email: string; + name: string | undefined; +}; + +export abstract class AccountService { + accounts$: Observable>; + activeAccount$: Observable<{ id: UserId | undefined } & AccountInfo>; + accountLock$: Observable; + accountLogout$: Observable; + /** + * Updates the `accounts$` observable with the new account data. + * @param userId + * @param accountData + */ + abstract addAccount(userId: UserId, accountData: AccountInfo): void; + /** + * updates the `accounts$` observable with the new preferred name for the account. + * @param userId + * @param name + */ + abstract setAccountName(userId: UserId, name: string): void; + /** + * updates the `accounts$` observable with the new email for the account. + * @param userId + * @param email + */ + abstract setAccountEmail(userId: UserId, email: string): void; + /** + * Updates the `accounts$` observable with the new account status. + * Also emits the `accountLock$` or `accountLogout$` observable if the status is `Locked` or `LoggedOut` respectively. + * @param userId + * @param status + */ + abstract setAccountStatus(userId: UserId, status: AuthenticationStatus): void; + /** + * Updates the `activeAccount$` observable with the new active account. + * @param userId + */ + abstract switchAccount(userId: UserId): void; +} export abstract class InternalAccountService extends AccountService { abstract delete(): void; diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts new file mode 100644 index 00000000000..3b28f39cf11 --- /dev/null +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -0,0 +1,181 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { trackEmissions } from "../../../spec/utils"; +import { LogService } from "../../platform/abstractions/log.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; +import { UserId } from "../../types/guid"; +import { AccountInfo } from "../abstractions/account.service"; +import { AuthenticationStatus } from "../enums/authentication-status"; + +import { AccountServiceImplementation } from "./account.service"; + +describe("accountService", () => { + let messagingService: MockProxy; + let logService: MockProxy; + let sut: AccountServiceImplementation; + const userId = "userId" as UserId; + function userInfo(status: AuthenticationStatus): AccountInfo { + return { status, email: "email", name: "name" }; + } + + beforeEach(() => { + messagingService = mock(); + logService = mock(); + + sut = new AccountServiceImplementation(messagingService, logService); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("activeAccount$", () => { + it("should emit undefined if no account is active", () => { + const emissions = trackEmissions(sut.activeAccount$); + + expect(emissions).toEqual([undefined]); + }); + + it("should emit the active account and status", async () => { + const emissions = trackEmissions(sut.activeAccount$); + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + sut.switchAccount(userId); + + expect(emissions).toEqual([ + undefined, // initial value + { id: userId, ...userInfo(AuthenticationStatus.Unlocked) }, + ]); + }); + + it("should remember the last emitted value", async () => { + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + sut.switchAccount(userId); + + expect(await firstValueFrom(sut.activeAccount$)).toEqual({ + id: userId, + ...userInfo(AuthenticationStatus.Unlocked), + }); + }); + }); + + describe("addAccount", () => { + it("should emit the new account", () => { + const emissions = trackEmissions(sut.accounts$); + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + + expect(emissions).toEqual([ + {}, // initial value + { [userId]: userInfo(AuthenticationStatus.Unlocked) }, + ]); + }); + }); + + describe("setAccountName", () => { + beforeEach(() => { + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + }); + + it("should emit the updated account", () => { + const emissions = trackEmissions(sut.accounts$); + sut.setAccountName(userId, "new name"); + + expect(emissions).toEqual([ + { [userId]: { ...userInfo(AuthenticationStatus.Unlocked), name: "name" } }, + { [userId]: { ...userInfo(AuthenticationStatus.Unlocked), name: "new name" } }, + ]); + }); + }); + + describe("setAccountEmail", () => { + beforeEach(() => { + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + }); + + it("should emit the updated account", () => { + const emissions = trackEmissions(sut.accounts$); + sut.setAccountEmail(userId, "new email"); + + expect(emissions).toEqual([ + { [userId]: { ...userInfo(AuthenticationStatus.Unlocked), email: "email" } }, + { [userId]: { ...userInfo(AuthenticationStatus.Unlocked), email: "new email" } }, + ]); + }); + }); + + describe("setAccountStatus", () => { + beforeEach(() => { + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + }); + + it("should not emit if the status is the same", async () => { + const emissions = trackEmissions(sut.accounts$); + sut.setAccountStatus(userId, AuthenticationStatus.Unlocked); + sut.setAccountStatus(userId, AuthenticationStatus.Unlocked); + + expect(emissions).toEqual([{ userId: userInfo(AuthenticationStatus.Unlocked) }]); + }); + + it("should maintain an accounts cache", async () => { + expect(await firstValueFrom(sut.accounts$)).toEqual({ + [userId]: userInfo(AuthenticationStatus.Unlocked), + }); + }); + + it("should emit if the status is different", () => { + const emissions = trackEmissions(sut.accounts$); + sut.setAccountStatus(userId, AuthenticationStatus.Locked); + + expect(emissions).toEqual([ + { userId: userInfo(AuthenticationStatus.Unlocked) }, // initial value from beforeEach + { userId: userInfo(AuthenticationStatus.Locked) }, + ]); + }); + + it("should emit logout if the status is logged out", () => { + const emissions = trackEmissions(sut.accountLogout$); + sut.setAccountStatus(userId, AuthenticationStatus.LoggedOut); + + expect(emissions).toEqual([userId]); + }); + + it("should emit lock if the status is locked", () => { + const emissions = trackEmissions(sut.accountLock$); + sut.setAccountStatus(userId, AuthenticationStatus.Locked); + + expect(emissions).toEqual([userId]); + }); + }); + + describe("switchAccount", () => { + let emissions: { id: string; status: AuthenticationStatus }[]; + + beforeEach(() => { + emissions = []; + sut.activeAccount$.subscribe((value) => emissions.push(value)); + }); + + it("should emit undefined if no account is provided", () => { + sut.switchAccount(undefined); + + expect(emissions).toEqual([undefined]); + }); + + it("should emit the active account and status", () => { + sut.addAccount(userId, userInfo(AuthenticationStatus.Unlocked)); + sut.switchAccount(userId); + sut.setAccountStatus(userId, AuthenticationStatus.Locked); + sut.switchAccount(undefined); + sut.switchAccount(undefined); + expect(emissions).toEqual([ + undefined, // initial value + { id: userId, ...userInfo(AuthenticationStatus.Unlocked) }, + { id: userId, ...userInfo(AuthenticationStatus.Locked) }, + ]); + }); + + it("should throw if switched to an unknown account", () => { + expect(() => sut.switchAccount(userId)).toThrowError("Account does not exist"); + }); + }); +}); diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index 02c12050952..33388218db3 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -1,16 +1,93 @@ -import { InternalAccountService } from "../../auth/abstractions/account.service"; +import { + BehaviorSubject, + Subject, + combineLatestWith, + map, + distinctUntilChanged, + shareReplay, +} from "rxjs"; + +import { AccountInfo, InternalAccountService } from "../../auth/abstractions/account.service"; import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; +import { UserId } from "../../types/guid"; +import { AuthenticationStatus } from "../enums/authentication-status"; export class AccountServiceImplementation implements InternalAccountService { + private accounts = new BehaviorSubject>({}); + private activeAccountId = new BehaviorSubject(undefined); + private lock = new Subject(); + private logout = new Subject(); + + accounts$ = this.accounts.asObservable(); + activeAccount$ = this.activeAccountId.pipe( + combineLatestWith(this.accounts$), + map(([id, accounts]) => (id ? { id, ...accounts[id] } : undefined)), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: false }) + ); + accountLock$ = this.lock.asObservable(); + accountLogout$ = this.logout.asObservable(); constructor(private messagingService: MessagingService, private logService: LogService) {} + addAccount(userId: UserId, accountData: AccountInfo): void { + this.accounts.value[userId] = accountData; + this.accounts.next(this.accounts.value); + } + + setAccountName(userId: UserId, name: string): void { + this.setAccountInfo(userId, { ...this.accounts.value[userId], name }); + } + + setAccountEmail(userId: UserId, email: string): void { + this.setAccountInfo(userId, { ...this.accounts.value[userId], email }); + } + + setAccountStatus(userId: UserId, status: AuthenticationStatus): void { + this.setAccountInfo(userId, { ...this.accounts.value[userId], status }); + + if (status === AuthenticationStatus.LoggedOut) { + this.logout.next(userId); + } else if (status === AuthenticationStatus.Locked) { + this.lock.next(userId); + } + } + + switchAccount(userId: UserId) { + if (userId == null) { + // indicates no account is active + this.activeAccountId.next(undefined); + return; + } + + if (this.accounts.value[userId] == null) { + throw new Error("Account does not exist"); + } + this.activeAccountId.next(userId); + } + + // TODO: update to use our own account status settings. Requires inverting direction of state service accounts flow async delete(): Promise { try { - this.messagingService.send("logout"); + this.messagingService?.send("logout"); } catch (e) { this.logService.error(e); throw e; } } + + private setAccountInfo(userId: UserId, accountInfo: AccountInfo) { + if (this.accounts.value[userId] == null) { + throw new Error("Account does not exist"); + } + + // Avoid unnecessary updates + // TODO: Faster comparison, maybe include a hash on the objects? + if (JSON.stringify(this.accounts.value[userId]) === JSON.stringify(accountInfo)) { + return; + } + + this.accounts.value[userId] = accountInfo; + this.accounts.next(this.accounts.value); + } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index d0983448d62..c8d45b6d4e3 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -6,6 +6,8 @@ import { OrganizationData } from "../../admin-console/models/data/organization.d import { PolicyData } from "../../admin-console/models/data/policy.data"; import { ProviderData } from "../../admin-console/models/data/provider.data"; import { Policy } from "../../admin-console/models/domain/policy"; +import { AccountService } from "../../auth/abstractions/account.service"; +import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable"; import { EnvironmentUrls } from "../../auth/models/domain/environment-urls"; import { ForceResetPasswordReason } from "../../auth/models/domain/force-reset-password-reason"; @@ -27,6 +29,7 @@ import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/ import { UsernameGeneratorOptions } from "../../tools/generator/username"; import { SendData } from "../../tools/send/models/data/send.data"; import { SendView } from "../../tools/send/models/view/send.view"; +import { UserId } from "../../types/guid"; import { CipherData } from "../../vault/models/data/cipher.data"; import { CollectionData } from "../../vault/models/data/collection.data"; import { FolderData } from "../../vault/models/data/folder.data"; @@ -110,6 +113,7 @@ export class StateService< protected memoryStorageService: AbstractMemoryStorageService, protected logService: LogService, protected stateFactory: StateFactory, + protected accountService: AccountService, protected useAccountCache: boolean = true ) { // If the account gets changed, verify the new account is unlocked @@ -168,6 +172,8 @@ export class StateService< } await this.pushAccounts(); this.activeAccountSubject.next(state.activeUserId); + // TODO: Temporary update to avoid routing all account status changes through account service for now. + this.accountService.switchAccount(state.activeUserId as UserId); return state; }); @@ -184,6 +190,12 @@ export class StateService< state.accounts[userId] = this.createAccount(); const diskAccount = await this.getAccountFromDisk({ userId: userId }); state.accounts[userId].profile = diskAccount.profile; + // TODO: Temporary update to avoid routing all account status changes through account service for now. + this.accountService.addAccount(userId as UserId, { + status: AuthenticationStatus.Locked, + name: diskAccount.profile.name, + email: diskAccount.profile.email, + }); return state; }); } @@ -198,6 +210,12 @@ export class StateService< }); await this.scaffoldNewAccountStorage(account); await this.setLastActive(new Date().getTime(), { userId: account.profile.userId }); + // TODO: Temporary update to avoid routing all account status changes through account service for now. + this.accountService.addAccount(account.profile.userId as UserId, { + status: AuthenticationStatus.Locked, + name: account.profile.name, + email: account.profile.email, + }); await this.setActiveUser(account.profile.userId); this.activeAccountSubject.next(account.profile.userId); } @@ -208,6 +226,9 @@ export class StateService< state.activeUserId = userId; await this.storageService.save(keys.activeUserId, userId); this.activeAccountSubject.next(state.activeUserId); + // TODO: temporary update to avoid routing all account status changes through account service for now. + this.accountService.switchAccount(userId as UserId); + return state; }); @@ -548,6 +569,9 @@ export class StateService< this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); + const nextStatus = value != null ? AuthenticationStatus.Unlocked : AuthenticationStatus.Locked; + this.accountService.setAccountStatus(options.userId as UserId, nextStatus); + if (options.userId == this.activeAccountSubject.getValue()) { const nextValue = value != null; @@ -581,6 +605,9 @@ export class StateService< this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); + const nextStatus = value != null ? AuthenticationStatus.Unlocked : AuthenticationStatus.Locked; + this.accountService.setAccountStatus(options.userId as UserId, nextStatus); + if (options?.userId == this.activeAccountSubject.getValue()) { const nextValue = value != null; @@ -3062,7 +3089,6 @@ export class StateService< this.reconcileOptions({ userId: account.profile.userId }, await this.defaultOnDiskOptions()) ); } - // protected async pushAccounts(): Promise { await this.pruneInMemoryAccounts(); @@ -3180,6 +3206,8 @@ export class StateService< return state; }); + // TODO: Invert this logic, we should remove accounts based on logged out emit + this.accountService.setAccountStatus(userId as UserId, AuthenticationStatus.LoggedOut); } protected async pruneInMemoryAccounts() { diff --git a/libs/common/src/types/guid.d.ts b/libs/common/src/types/guid.d.ts new file mode 100644 index 00000000000..f77655a95f3 --- /dev/null +++ b/libs/common/src/types/guid.d.ts @@ -0,0 +1,5 @@ +import { Opaque } from "type-fest"; + +type Guid = Opaque; + +type UserId = Opaque; From 87dbe8997dfa8dc78c310a8adf9c1df4ac1edc47 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:33:41 +0200 Subject: [PATCH 16/79] [PM-4209] Enable importing on browser (#6503) * Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Enable importing on browser Create import-dialog Add routing and routing animations Settings import items no longer navigates to help page but opens import page Extend messages.json to include all the necessary messages from shared components * Fix back navigation * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * add loading and disabled state to import-browser * override popup header styles * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * Enable importing on browser Create import-dialog Add routing and routing animations Settings import items no longer navigates to help page but opens import page Extend messages.json to include all the necessary messages from shared components * Fix back navigation * add loading and disabled state to import-browser * override popup header styles * Add missing message for importBlockedByPolicy callout * Implement onSuccessfulImport to navigate back to settings * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * rename selector * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * Lazy-load the ImportWebComponent via both routes * Fix import path for ImportComponent * Navigate to import opens in popout when navigated from the popup Make import call async and await router navigate - If the user has the popup open and selects import, it will navigate to the import page and popout into a new window. This is necessary as any focus-loss (i.e Choose file) would close the popup. - If the user is using the for example the sidebar or an already popped out window, just navigate to import page * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Update description of "importData" in messages.json * Add missing messages for file-password-prompt * Add missing messages for import-error-dialog * Add missing message for import-success-dialog * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith * Add missing importWarning --------- Co-authored-by: Daniel James Smith Co-authored-by: William Martin --- apps/browser/src/_locales/en/messages.json | 111 ++++++++++++++++++ .../src/popup/app-routing.animations.ts | 3 + apps/browser/src/popup/app-routing.module.ts | 7 ++ apps/browser/src/popup/scss/base.scss | 2 +- .../src/popup/settings/settings.component.ts | 7 +- .../import/import-browser.component.html | 24 ++++ .../import/import-browser.component.ts | 31 +++++ 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/tools/popup/settings/import/import-browser.component.html create mode 100644 apps/browser/src/tools/popup/settings/import/import-browser.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9a68f8bb9fb..24e1bc3ce30 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1656,6 +1656,9 @@ "personalOwnershipPolicyInEffect": { "message": "An organization policy is affecting your ownership options." }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2449,6 +2452,114 @@ "message": "Turn off master password re-prompt to edit this field", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, + "importData": { + "message": "Import data", + "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "resolveTheErrorsBelowAndTryAgain": { + "message": "Resolve the errors below and try again." + }, + "description": { + "message": "Description" + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importSuccessNumberOfItems": { + "message": "A total of $AMOUNT$ items were imported.", + "placeholders": { + "amount": { + "content": "$1", + "example": "2" + } + } + }, + "total": { + "message": "Total" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "importDestination": { + "message": "Import destination" + }, + "learnAboutImportOptions": { + "message": "Learn about your import options" + }, + "selectImportFolder": { + "message": "Select a folder" + }, + "selectImportCollection": { + "message": "Select a collection" + }, + "importTargetHint": { + "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", + "placeholders": { + "destination": { + "content": "$1", + "example": "folder or collection" + } + } + }, + "importUnassignedItemsError": { + "message": "File contains unassigned items." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "confirmFilePassword": { + "message": "Confirm file password" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, diff --git a/apps/browser/src/popup/app-routing.animations.ts b/apps/browser/src/popup/app-routing.animations.ts index 2304944acb0..42baf65c270 100644 --- a/apps/browser/src/popup/app-routing.animations.ts +++ b/apps/browser/src/popup/app-routing.animations.ts @@ -174,6 +174,9 @@ export const routerTransition = trigger("routerTransition", [ transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft), transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight), + transition("tabs => import", inSlideLeft), + transition("import => tabs", outSlideRight), + transition("tabs => export", inSlideLeft), transition("export => tabs", outSlideRight), diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 10159a715f0..df7b9ffb1cf 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -32,6 +32,7 @@ import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.componen import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; import { SendTypeComponent } from "../tools/popup/send/send-type.component"; import { ExportComponent } from "../tools/popup/settings/export.component"; +import { ImportBrowserComponent } from "../tools/popup/settings/import/import-browser.component"; import { Fido2Component } from "../vault/popup/components/fido2/fido2.component"; import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; @@ -222,6 +223,12 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "generator-history" }, }, + { + path: "import", + component: ImportBrowserComponent, + canActivate: [AuthGuard], + data: { state: "import" }, + }, { path: "export", component: ExportComponent, diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 3b401d356f5..6cd99abb0d5 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -175,7 +175,7 @@ cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, } } -header { +header:not(bit-callout header) { min-height: 44px; max-height: 44px; display: flex; diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index 274ed871225..252a2097156 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -473,8 +473,11 @@ export class SettingsComponent implements OnInit { BrowserApi.createNewTab(url); } - import() { - BrowserApi.createNewTab("https://bitwarden.com/help/import-data/"); + async import() { + await this.router.navigate(["/import"]); + if (await BrowserApi.isPopupOpen()) { + this.popupUtilsService.popOut(window); + } } export() { diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.html b/apps/browser/src/tools/popup/settings/import/import-browser.component.html new file mode 100644 index 00000000000..b305e6c395f --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser.component.html @@ -0,0 +1,24 @@ +
+
+ +
+

+ {{ "importData" | i18n }} +

+
+ +
+
+
+ +
diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts new file mode 100644 index 00000000000..3fea3aad04b --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts @@ -0,0 +1,31 @@ +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"; + +@Component({ + templateUrl: "import-browser.component.html", + standalone: true, + imports: [ + CommonModule, + RouterLink, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + ], +}) +export class ImportBrowserComponent { + protected disabled = false; + protected loading = false; + + constructor(private router: Router) {} + + protected async onSuccessfulImport(organizationId: string): Promise { + this.router.navigate(["/tabs/settings"]); + } +} From e9f0c07b02c539a365bb68c678c31f1ba4e04dd8 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Thu, 19 Oct 2023 17:56:51 -0400 Subject: [PATCH 17/79] [SM-949] Add Event Logs to Service Account (#6546) * Add Event Logs to Service Account * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Add takeUntil import * add service account access guard --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Thomas Avery --- .../service-account-event-log-api.service.ts | 43 +++++++ .../service-accounts-events.component.html | 105 ++++++++++++++++++ .../service-accounts-events.component.ts | 77 +++++++++++++ .../guards/service-account-access.guard.ts | 28 +++++ .../service-account.component.html | 1 + .../service-accounts-routing.module.ts | 7 ++ .../service-accounts.module.ts | 2 + 7 files changed, 263 insertions(+) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts new file mode 100644 index 00000000000..669c063e988 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EventResponse } from "@bitwarden/common/models/response/event.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +@Injectable({ + providedIn: "root", +}) +export class ServiceAccountEventLogApiService { + constructor(private apiService: ApiService) {} + + async getEvents( + serviceAccountId: string, + start: string, + end: string, + token: string + ): Promise> { + const r = await this.apiService.send( + "GET", + this.addEventParameters("/sm/events/service-accounts/" + serviceAccountId, start, end, token), + null, + true, + true + ); + return new ListResponse(r, EventResponse); + } + + private addEventParameters(base: string, start: string, end: string, token: string) { + if (start != null) { + base += "?start=" + start; + } + if (end != null) { + base += base.indexOf("?") > -1 ? "&" : "?"; + base += "end=" + end; + } + if (token != null) { + base += base.indexOf("?") > -1 ? "&" : "?"; + base += "continuationToken=" + token; + } + return base; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html new file mode 100644 index 00000000000..e5a7ce64da8 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html @@ -0,0 +1,105 @@ +
+

{{ "eventLogs" | i18n }}

+
+ + {{ "from" | i18n }} + + + - + + {{ "to" | i18n }} + + +
+ +
+
+ +
+
+
+ + + {{ "loading" | i18n }} + + +

{{ "noEventsInList" | i18n }}

+ + + + {{ "timestamp" | i18n }} + {{ "client" | i18n }} + {{ "event" | i18n }} + + + + + {{ e.date | date : "medium" }} + + {{ e.appName }} + + + + + + +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts new file mode 100644 index 00000000000..652272ecd1a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -0,0 +1,77 @@ +import { Component, OnDestroy } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { BaseEventsComponent } from "@bitwarden/web-vault/app/common/base.events.component"; +import { EventService } from "@bitwarden/web-vault/app/core"; +import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export"; + +import { ServiceAccountEventLogApiService } from "./service-account-event-log-api.service"; + +@Component({ + selector: "sm-service-accounts-events", + templateUrl: "./service-accounts-events.component.html", +}) +export class ServiceAccountEventsComponent extends BaseEventsComponent implements OnDestroy { + exportFileName = "service-account-events"; + private destroy$ = new Subject(); + private serviceAccountId: string; + + constructor( + eventService: EventService, + private serviceAccountEventsApiService: ServiceAccountEventLogApiService, + private route: ActivatedRoute, + i18nService: I18nService, + exportService: EventExportService, + platformUtilsService: PlatformUtilsService, + logService: LogService, + fileDownloadService: FileDownloadService + ) { + super( + eventService, + i18nService, + exportService, + platformUtilsService, + logService, + fileDownloadService + ); + } + + async ngOnInit() { + // eslint-disable-next-line rxjs/no-async-subscribe + this.route.params.pipe(takeUntil(this.destroy$)).subscribe(async (params) => { + this.serviceAccountId = params.serviceAccountId; + await this.load(); + }); + } + + async load() { + await this.loadEvents(true); + this.loaded = true; + } + + protected requestEvents(startDate: string, endDate: string, continuationToken: string) { + return this.serviceAccountEventsApiService.getEvents( + this.serviceAccountId, + startDate, + endDate, + continuationToken + ); + } + + protected getUserName() { + return { + name: this.i18nService.t("serviceAccount") + " " + this.serviceAccountId, + email: "", + }; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts new file mode 100644 index 00000000000..a1c54a6bfaa --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts @@ -0,0 +1,28 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; + +import { ServiceAccountService } from "../service-account.service"; + +/** + * Redirects to service accounts page if the user doesn't have access to service account. + */ +export const serviceAccountAccessGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const serviceAccountService = inject(ServiceAccountService); + + try { + const serviceAccount = await serviceAccountService.getByServiceAccountId( + route.params.serviceAccountId, + route.params.organizationId + ); + if (serviceAccount) { + return true; + } + } catch { + return createUrlTreeFromSnapshot(route, [ + "/sm", + route.params.organizationId, + "service-accounts", + ]); + } + return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "service-accounts"]); +}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html index 7d6304b5a0e..8b7991b1214 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html @@ -13,6 +13,7 @@ {{ "projects" | i18n }} {{ "people" | i18n }} {{ "accessTokens" | i18n }} + {{ "eventLogs" | i18n }}
-
diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts index 30ead216b50..3fea3aad04b 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts +++ b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts @@ -1,14 +1,11 @@ import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +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 { FilePopoutCalloutComponent } from "../../components/file-popout-callout.component"; -import { FilePopoutUtilsService } from "../../services/file-popout-utils.service"; - @Component({ templateUrl: "import-browser.component.html", standalone: true, @@ -20,20 +17,13 @@ import { FilePopoutUtilsService } from "../../services/file-popout-utils.service AsyncActionsModule, ButtonModule, ImportComponent, - FilePopoutCalloutComponent, ], }) -export class ImportBrowserComponent implements OnInit { +export class ImportBrowserComponent { protected disabled = false; protected loading = false; - protected hideFileSelector = false; - - constructor(private router: Router, private filePopoutUtilsService: FilePopoutUtilsService) {} - - ngOnInit(): void { - this.hideFileSelector = this.filePopoutUtilsService.showFilePopoutMessage(window); - } + constructor(private router: Router) {} protected async onSuccessfulImport(organizationId: string): Promise { this.router.navigate(["/tabs/settings"]); diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index ab9250a4c73..83e119fcc57 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -344,7 +344,7 @@ and save the zip file.
- + {{ "selectImportFile" | i18n }}
+
+ + +
diff --git a/apps/desktop/src/app/tools/generator.component.html b/apps/desktop/src/app/tools/generator.component.html index 1b2ee9df42e..0c66ebde805 100644 --- a/apps/desktop/src/app/tools/generator.component.html +++ b/apps/desktop/src/app/tools/generator.component.html @@ -380,6 +380,16 @@

(blur)="saveUsernameOptions()" />

+
+ + +
diff --git a/apps/web/src/app/tools/generator.component.html b/apps/web/src/app/tools/generator.component.html index c690a458dab..2e6d6d0effd 100644 --- a/apps/web/src/app/tools/generator.component.html +++ b/apps/web/src/app/tools/generator.component.html @@ -283,6 +283,17 @@

{{ "generator" | i18n }}

(blur)="saveUsernameOptions()" />
+
+ + +
diff --git a/libs/common/src/tools/generator/username/email-forwarders/forwarder-options.ts b/libs/common/src/tools/generator/username/email-forwarders/forwarder-options.ts index cca6dd34dd1..00d1717bf60 100644 --- a/libs/common/src/tools/generator/username/email-forwarders/forwarder-options.ts +++ b/libs/common/src/tools/generator/username/email-forwarders/forwarder-options.ts @@ -4,6 +4,7 @@ export class ForwarderOptions { fastmail = new FastmailForwarderOptions(); anonaddy = new AnonAddyForwarderOptions(); forwardemail = new ForwardEmailForwarderOptions(); + simplelogin = new SimpleLoginForwarderOptions(); } export class FastmailForwarderOptions { @@ -18,3 +19,7 @@ export class AnonAddyForwarderOptions { export class ForwardEmailForwarderOptions { domain: string; } + +export class SimpleLoginForwarderOptions { + baseUrl: string; +} diff --git a/libs/common/src/tools/generator/username/email-forwarders/simple-login-forwarder.ts b/libs/common/src/tools/generator/username/email-forwarders/simple-login-forwarder.ts index 7ecd72dc59c..4d5b7749d49 100644 --- a/libs/common/src/tools/generator/username/email-forwarders/simple-login-forwarder.ts +++ b/libs/common/src/tools/generator/username/email-forwarders/simple-login-forwarder.ts @@ -17,7 +17,7 @@ export class SimpleLoginForwarder implements Forwarder { "Content-Type": "application/json", }), }; - let url = "https://app.simplelogin.io/api/alias/random/new"; + let url = options.simplelogin.baseUrl + "/api/alias/random/new"; if (options.website != null) { url += "?hostname=" + options.website; } diff --git a/libs/common/src/tools/generator/username/username-generation-options.ts b/libs/common/src/tools/generator/username/username-generation-options.ts index 970f7e945e3..276668de96a 100644 --- a/libs/common/src/tools/generator/username/username-generation-options.ts +++ b/libs/common/src/tools/generator/username/username-generation-options.ts @@ -17,4 +17,5 @@ export type UsernameGeneratorOptions = { forwardedForwardEmailApiToken?: string; forwardedForwardEmailDomain?: string; forwardedSimpleLoginApiKey?: string; + forwardedSimpleLoginBaseUrl?: string; }; diff --git a/libs/common/src/tools/generator/username/username-generation.service.ts b/libs/common/src/tools/generator/username/username-generation.service.ts index 35a3a73da90..e28ffb12221 100644 --- a/libs/common/src/tools/generator/username/username-generation.service.ts +++ b/libs/common/src/tools/generator/username/username-generation.service.ts @@ -26,6 +26,7 @@ const DefaultOptions: UsernameGeneratorOptions = { forwardedAnonAddyDomain: "anonaddy.me", forwardedAnonAddyBaseUrl: "https://app.addy.io", forwardedForwardEmailDomain: "hideaddress.net", + forwardedSimpleLoginBaseUrl: "https://app.simplelogin.io", }; export class UsernameGenerationService implements UsernameGenerationServiceAbstraction { @@ -128,6 +129,7 @@ export class UsernameGenerationService implements UsernameGenerationServiceAbstr if (o.forwardedService === "simplelogin") { forwarder = new SimpleLoginForwarder(); forwarderOptions.apiKey = o.forwardedSimpleLoginApiKey; + forwarderOptions.simplelogin.baseUrl = o.forwardedSimpleLoginBaseUrl; } else if (o.forwardedService === "anonaddy") { forwarder = new AnonAddyForwarder(); forwarderOptions.apiKey = o.forwardedAnonAddyApiToken; From 95d4d281cb1eb3171f31e277df0a5b771cc2f889 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:01:59 -0400 Subject: [PATCH 34/79] [AC-1706] Show Discounted Prices (#6668) * Removed subscription copy from org and individual * Discount all prices in subscription components --- .../user-subscription.component.html | 11 ------ .../individual/user-subscription.component.ts | 4 --- ...nization-subscription-cloud.component.html | 14 ++------ ...ganization-subscription-cloud.component.ts | 35 +++++++++++++++---- .../sm-subscribe-standalone.component.html | 1 + .../sm-subscribe-standalone.component.ts | 2 ++ .../billing/shared/sm-subscribe.component.ts | 19 +++++++--- apps/web/src/locales/en/messages.json | 6 ---- .../organization-subscription.response.ts | 28 ++++++++++++--- .../models/response/subscription.response.ts | 14 -------- 10 files changed, 73 insertions(+), 61 deletions(-) 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 dca77dbf950..4c600b421c2 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -90,17 +90,6 @@

{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} - - - - {{ "customBillingStart" | i18n }} - - {{ "billingHistory" | i18n }} - - {{ "customBillingEnd" | i18n }} - - -

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 5b4b7cf49ef..abb5fd06428 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -205,10 +205,6 @@ export class UserSubscriptionComponent implements OnInit { return this.sub != null ? this.sub.upcomingInvoice : null; } - get discount() { - return this.sub != null ? this.sub.discount : null; - } - get storagePercentage() { return this.sub != null && this.sub.maxStorageGb ? +(100 * (this.sub.storageGb / this.sub.maxStorageGb)).toFixed(2) diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index bfb94a389ed..62d17a7e00d 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -69,7 +69,7 @@

- + {{ i.productName }} - {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ @@ -79,17 +79,6 @@

{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} - - - - {{ "customBillingStart" | i18n }} - - {{ "billingHistory" | i18n }} - - {{ "customBillingEnd" | i18n }} - - - @@ -150,6 +139,7 @@

diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index eb7180f7c83..d9e81e5f6b0 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -134,12 +134,24 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy return this.sub != null ? this.sub.subscription : null; } + get subscriptionLineItems() { + return this.lineItems.map((lineItem: BillingSubscriptionItemResponse) => ({ + name: lineItem.name, + amount: this.discountPrice(lineItem.amount), + quantity: lineItem.quantity, + interval: lineItem.interval, + sponsoredSubscriptionItem: lineItem.sponsoredSubscriptionItem, + addonSubscriptionItem: lineItem.addonSubscriptionItem, + productName: lineItem.productName, + })); + } + get nextInvoice() { return this.sub != null ? this.sub.upcomingInvoice : null; } - get discount() { - return this.sub != null ? this.sub.discount : null; + get customerDiscount() { + return this.sub != null ? this.sub.customerDiscount : null; } get isExpired() { @@ -168,11 +180,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } get storageGbPrice() { - return this.sub.plan.PasswordManager.additionalStoragePricePerGb; + return this.discountPrice(this.sub.plan.PasswordManager.additionalStoragePricePerGb); } get seatPrice() { - return this.sub.plan.PasswordManager.seatPrice; + return this.discountPrice(this.sub.plan.PasswordManager.seatPrice); } get seats() { @@ -183,12 +195,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy return { seatCount: this.sub.smSeats, maxAutoscaleSeats: this.sub.maxAutoscaleSmSeats, - seatPrice: this.sub.plan.SecretsManager.seatPrice, + seatPrice: this.discountPrice(this.sub.plan.SecretsManager.seatPrice), maxAutoscaleServiceAccounts: this.sub.maxAutoscaleSmServiceAccounts, additionalServiceAccounts: this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount, interval: this.sub.plan.isAnnual ? "year" : "month", - additionalServiceAccountPrice: this.sub.plan.SecretsManager.additionalPricePerServiceAccount, + additionalServiceAccountPrice: this.discountPrice( + this.sub.plan.SecretsManager.additionalPricePerServiceAccount + ), baseServiceAccountCount: this.sub.plan.SecretsManager.baseServiceAccount, }; } @@ -382,6 +396,15 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } }; + discountPrice = (price: number) => { + const discount = + !!this.customerDiscount && this.customerDiscount.active + ? price * (this.customerDiscount.percentOff / 100) + : 0; + + return price - discount; + }; + get showChangePlanButton() { return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan; } diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html index 84c74ee4282..2f3a2c08e30 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html @@ -4,5 +4,6 @@ [selectedPlan]="plan" [upgradeOrganization]="false" [showSubmitButton]="true" + [customerDiscount]="customerDiscount" > diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 2942a67560f..ebde4ab2536 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -6,6 +6,7 @@ import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin- import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request"; +import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -19,6 +20,7 @@ import { secretsManagerSubscribeFormFactory } from "../shared"; export class SecretsManagerSubscribeStandaloneComponent { @Input() plan: PlanResponse; @Input() organization: Organization; + @Input() customerDiscount: BillingCustomerDiscount; @Output() onSubscribe = new EventEmitter(); formGroup = secretsManagerSubscribeFormFactory(this.formBuilder); diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts index 1aa6c1bccb5..85836bf17f2 100644 --- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts +++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts @@ -3,6 +3,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { Subject, startWith, takeUntil } from "rxjs"; import { ControlsOf } from "@bitwarden/angular/types/controls-of"; +import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { ProductType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -36,6 +37,7 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { @Input() upgradeOrganization: boolean; @Input() showSubmitButton = false; @Input() selectedPlan: PlanResponse; + @Input() customerDiscount: BillingCustomerDiscount; logo = SecretsManagerLogo; productTypes = ProductType; @@ -63,6 +65,15 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { this.destroy$.complete(); } + discountPrice = (price: number) => { + const discount = + !!this.customerDiscount && this.customerDiscount.active + ? price * (this.customerDiscount.percentOff / 100) + : 0; + + return price - discount; + }; + get product() { return this.selectedPlan.product; } @@ -84,8 +95,8 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { get monthlyCostPerServiceAccount() { return this.selectedPlan.isAnnual - ? this.selectedPlan.SecretsManager.additionalPricePerServiceAccount / 12 - : this.selectedPlan.SecretsManager.additionalPricePerServiceAccount; + ? this.discountPrice(this.selectedPlan.SecretsManager.additionalPricePerServiceAccount) / 12 + : this.discountPrice(this.selectedPlan.SecretsManager.additionalPricePerServiceAccount); } get maxUsers() { @@ -98,7 +109,7 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { get monthlyCostPerUser() { return this.selectedPlan.isAnnual - ? this.selectedPlan.SecretsManager.seatPrice / 12 - : this.selectedPlan.SecretsManager.seatPrice; + ? this.discountPrice(this.selectedPlan.SecretsManager.seatPrice) / 12 + : this.discountPrice(this.selectedPlan.SecretsManager.seatPrice); } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5b4b2ac8625..3f7b1d1b05e 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7273,12 +7273,6 @@ "alreadyHaveAccount": { "message": "Already have an account?" }, - "customBillingStart": { - "message": "Custom billing is not reflected. Visit the " - }, - "customBillingEnd": { - "message": " page for latest invoicing." - }, "typePasskey": { "message": "Passkey" }, diff --git a/libs/common/src/billing/models/response/organization-subscription.response.ts b/libs/common/src/billing/models/response/organization-subscription.response.ts index a86adbabe7c..404540c6165 100644 --- a/libs/common/src/billing/models/response/organization-subscription.response.ts +++ b/libs/common/src/billing/models/response/organization-subscription.response.ts @@ -1,9 +1,9 @@ import { OrganizationResponse } from "../../../admin-console/models/response/organization.response"; +import { BaseResponse } from "../../../models/response/base.response"; import { BillingSubscriptionResponse, BillingSubscriptionUpcomingInvoiceResponse, - BillingCustomerDiscount, } from "./subscription.response"; export class OrganizationSubscriptionResponse extends OrganizationResponse { @@ -11,7 +11,7 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse { storageGb: number; subscription: BillingSubscriptionResponse; upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; - discount: BillingCustomerDiscount; + customerDiscount: BillingCustomerDiscount; expiration: string; expirationWithoutGracePeriod: string; secretsManagerBeta: boolean; @@ -27,10 +27,30 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse { upcomingInvoice == null ? null : new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); - const discount = this.getResponseProperty("Discount"); - this.discount = discount == null ? null : new BillingCustomerDiscount(discount); + const customerDiscount = this.getResponseProperty("CustomerDiscount"); + this.customerDiscount = + customerDiscount == null ? null : new BillingCustomerDiscount(customerDiscount); this.expiration = this.getResponseProperty("Expiration"); this.expirationWithoutGracePeriod = this.getResponseProperty("ExpirationWithoutGracePeriod"); this.secretsManagerBeta = this.getResponseProperty("SecretsManagerBeta"); } } + +export class BillingCustomerDiscount extends BaseResponse { + id: string; + active: boolean; + percentOff?: number; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.active = this.getResponseProperty("Active"); + this.percentOff = this.getResponseProperty("PercentOff"); + } + + discountPrice = (price: number) => { + const discount = this !== null && this.active ? price * (this.percentOff / 100) : 0; + + return price - discount; + }; +} diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts index fd84cf493d4..d6bff87adb0 100644 --- a/libs/common/src/billing/models/response/subscription.response.ts +++ b/libs/common/src/billing/models/response/subscription.response.ts @@ -6,7 +6,6 @@ export class SubscriptionResponse extends BaseResponse { maxStorageGb: number; subscription: BillingSubscriptionResponse; upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; - discount: BillingCustomerDiscount; license: any; expiration: string; usingInAppPurchase: boolean; @@ -21,13 +20,11 @@ export class SubscriptionResponse extends BaseResponse { this.usingInAppPurchase = this.getResponseProperty("UsingInAppPurchase"); const subscription = this.getResponseProperty("Subscription"); const upcomingInvoice = this.getResponseProperty("UpcomingInvoice"); - const discount = this.getResponseProperty("Discount"); this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); this.upcomingInvoice = upcomingInvoice == null ? null : new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); - this.discount = discount == null ? null : new BillingCustomerDiscount(discount); } } @@ -89,14 +86,3 @@ export class BillingSubscriptionUpcomingInvoiceResponse extends BaseResponse { this.amount = this.getResponseProperty("Amount"); } } - -export class BillingCustomerDiscount extends BaseResponse { - id: string; - active: boolean; - - constructor(response: any) { - super(response); - this.id = this.getResponseProperty("Id"); - this.active = this.getResponseProperty("Active"); - } -} From c2e03d2cdc8943e1d95a13a517a192a081906e0a Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 23 Oct 2023 17:22:07 +0200 Subject: [PATCH 35/79] [PM-2052] Migrate bulk restore component (#6604) --- .../bulk/bulk-restore-revoke.component.html | 183 ++++++++---------- .../bulk/bulk-restore-revoke.component.ts | 30 +-- .../organizations/members/people.component.ts | 13 +- 3 files changed, 109 insertions(+), 117 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html index db8af13ed27..d05fed4f922 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html @@ -1,101 +1,88 @@ -