From 591f44438a1ff30c269b084117aefa2e8c0bdfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Tue, 25 Jun 2024 02:09:09 +0100 Subject: [PATCH 01/12] =?UTF-8?q?[AC-1609]=C2=A0Update=20domain=20name=20v?= =?UTF-8?q?alidation=20regex=20to=20support=20single=20letter=20name=20(#9?= =?UTF-8?q?336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validators/domain-name.validator.spec.ts | 37 +++++++++++++++++++ .../validators/domain-name.validator.ts | 30 +++++++-------- 2 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.spec.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.spec.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.spec.ts new file mode 100644 index 0000000000..44eaa17875 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.spec.ts @@ -0,0 +1,37 @@ +import { AbstractControl, ValidationErrors } from "@angular/forms"; + +import { domainNameValidator } from "./domain-name.validator"; + +describe("domainNameValidator", () => { + let validatorFn: (control: AbstractControl) => ValidationErrors | null; + const errorMessage = "Invalid domain name"; + + beforeEach(() => { + validatorFn = domainNameValidator(errorMessage); + }); + + const testCases = [ + { value: "e.com", expected: null }, + { value: "example.com", expected: null }, + { value: "sub.example.com", expected: null }, + { value: "sub.sub.example.com", expected: null }, + { value: "example.co.uk", expected: null }, + { value: "example", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "-example.com", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "example-.com", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "example..com", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "http://example.com", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "www.example.com", expected: { invalidDomainName: { message: errorMessage } } }, + { value: "", expected: null }, + { value: "x".repeat(64) + ".com", expected: { invalidDomainName: { message: errorMessage } } }, + ]; + + describe("run test cases", () => { + testCases.forEach(({ value, expected }) => { + test(`should return ${JSON.stringify(expected)} for value "${value}"`, () => { + const control = { value } as AbstractControl; + expect(validatorFn(control)).toEqual(expected); + }); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.ts index e49ca16e19..55682c891f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/validators/domain-name.validator.ts @@ -13,24 +13,22 @@ export function domainNameValidator(errorMessage: string): ValidatorFn { // We do not want any prefixes per industry standards. // Must support top-level domains and any number of subdomains. - // / # start regex - // ^ # start of string - // (?!(http(s)?:\/\/|www\.)) # negative lookahead to check if input doesn't match "http://", "https://" or "www." - // [a-zA-Z0-9] # first character must be a letter or a number - // [a-zA-Z0-9-]{0,61} # domain name can have 0 to 61 characters that are letters, numbers, or hyphens - // [a-zA-Z0-9] # domain name must end with a letter or a number - // (?: # start of non-capturing group (subdomain sections are optional) - // \. # subdomain must have a period - // [a-zA-Z0-9] # first character of subdomain must be a letter or a number - // [a-zA-Z0-9-]{0,61} # subdomain can have 0 to 61 characters that are letters, numbers, or hyphens - // [a-zA-Z0-9] # subdomain must end with a letter or a number - // )* # end of non-capturing group (subdomain sections are optional) - // \. # domain name must have a period - // [a-zA-Z]{2,} # domain name must have at least two letters (the domain extension) - // $/ # end of string + // / # start regex + // ^ # start of string + // (?!(http(s)?:\/\/|www\.)) # negative lookahead to check if input doesn't match "http://", "https://" or "www." + // ( # start of capturing group for the entire domain + // [a-zA-Z0-9] # first character of domain must be a letter or a number + // ( # start of optional group for subdomain or domain section + // [a-zA-Z0-9-]{0,61} # subdomain/domain section can have 0 to 61 characters that are letters, numbers, or hyphens + // [a-zA-Z0-9] # subdomain/domain section must end with a letter or a number + // )? # end of optional group for subdomain or domain section + // \. # subdomain/domain section must have a period + // )+ # end of capturing group for the entire domain, repeatable for subdomains + // [a-zA-Z]{2,} # domain name must have at least two letters (the domain extension) + // $/ # end of string const validDomainNameRegex = - /^(?!(http(s)?:\/\/|www\.))[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])*\.[a-zA-Z]{2,}$/; + /^(?!(http(s)?:\/\/|www\.))([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/; const invalid = !validDomainNameRegex.test(control.value); From dce5c0f184ab64df4715cff1c16b8352b6afa617 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 25 Jun 2024 11:06:04 +0200 Subject: [PATCH 02/12] [PM-6413] Add http loophole for localhost (#9236) * [PM-6413] feat: add http loophole for localhost Fixes #6882 * feat: add sanity check * feat: change fido2 filters to allow scripts on localhost * [PM-6413] fix: injection tests --- .../fido2/background/fido2.background.spec.ts | 2 +- .../fido2/background/fido2.background.ts | 2 +- .../fido2/content/fido2-content-script.ts | 4 +- .../fido2/content/fido2-page-script.ts | 4 +- ...do2-page-script.webauthn-supported.spec.ts | 9 +-- .../fido2/fido2-client.service.spec.ts | 58 +++++++++++++++++++ .../services/fido2/fido2-client.service.ts | 10 +++- 7 files changed, 79 insertions(+), 10 deletions(-) diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index f0c5fc5695..43d07bdea1 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -32,7 +32,7 @@ const contentScriptDetails = { ...sharedScriptInjectionDetails, }; const sharedRegistrationOptions = { - matches: ["https://*/*"], + matches: ["https://*/*", "http://localhost/*"], excludeMatches: ["https://*/*.xml*"], allFrames: true, ...sharedExecuteScriptOptions, diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index a58d32da16..0524a2529f 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -33,7 +33,7 @@ export class Fido2Background implements Fido2BackgroundInterface { runAt: "document_start", }; private readonly sharedRegistrationOptions: SharedFido2ScriptRegistrationOptions = { - matches: ["https://*/*"], + matches: ["https://*/*", "http://localhost/*"], excludeMatches: ["https://*/*.xml*"], allFrames: true, ...this.sharedInjectionDetails, diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts index ad9f526f6c..171aa7cd83 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts @@ -17,7 +17,9 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; (function (globalContext) { const shouldExecuteContentScript = globalContext.document.contentType === "text/html" && - globalContext.document.location.protocol === "https:"; + (globalContext.document.location.protocol === "https:" || + (globalContext.document.location.protocol === "http:" && + globalContext.document.location.hostname === "localhost")); if (!shouldExecuteContentScript) { return; diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index cf14471359..5f91e6c081 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -8,7 +8,9 @@ import { Messenger } from "./messaging/messenger"; (function (globalContext) { const shouldExecuteContentScript = globalContext.document.contentType === "text/html" && - globalContext.document.location.protocol === "https:"; + (globalContext.document.location.protocol === "https:" || + (globalContext.document.location.protocol === "http:" && + globalContext.document.location.hostname === "localhost")); if (!shouldExecuteContentScript) { return; diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 46ef4d60d6..292d0e0118 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -16,8 +16,9 @@ const mockGlobalThisDocument = { contentType: "text/html", location: { ...originalGlobalThis.document.location, - href: "https://localhost", - origin: "https://localhost", + href: "https://bitwarden.com", + origin: "https://bitwarden.com", + hostname: "bitwarden.com", protocol: "https:", }, }; @@ -166,8 +167,8 @@ describe("Fido2 page script with native WebAuthn support", () => { ...mockGlobalThisDocument, location: { ...mockGlobalThisDocument.location, - href: "http://localhost", - origin: "http://localhost", + href: "http://bitwarden.com", + origin: "http://bitwarden.com", protocol: "http:", }, })); diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index 13e1d8f282..597f2d8f32 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -19,6 +19,7 @@ import { } from "../../abstractions/fido2/fido2-client.service.abstraction"; import { Utils } from "../../misc/utils"; +import * as DomainUtils from "./domain-utils"; import { Fido2AuthenticatorService } from "./fido2-authenticator.service"; import { Fido2ClientService } from "./fido2-client.service"; import { Fido2Utils } from "./fido2-utils"; @@ -36,6 +37,7 @@ describe("FidoAuthenticatorService", () => { let domainSettingsService: MockProxy; let client!: Fido2ClientService; let tab!: chrome.tabs.Tab; + let isValidRpId!: jest.SpyInstance; beforeEach(async () => { authenticator = mock(); @@ -44,6 +46,8 @@ describe("FidoAuthenticatorService", () => { vaultSettingsService = mock(); domainSettingsService = mock(); + isValidRpId = jest.spyOn(DomainUtils, "isValidRpId"); + client = new Fido2ClientService( authenticator, configService, @@ -58,6 +62,10 @@ describe("FidoAuthenticatorService", () => { tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; }); + afterEach(() => { + isValidRpId.mockRestore(); + }); + describe("createCredential", () => { describe("input parameters validation", () => { // Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException. @@ -113,6 +121,7 @@ describe("FidoAuthenticatorService", () => { }); // Spec: If options.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain, return a DOMException whose name is "SecurityError", and terminate this algorithm. + // This is actually checked by `isValidRpId` function, but we'll test it here as well it("should throw error if rp.id is not valid for this origin", async () => { const params = createParams({ origin: "https://passwordless.dev", @@ -126,6 +135,20 @@ describe("FidoAuthenticatorService", () => { await rejects.toBeInstanceOf(DOMException); }); + // Sanity check to make sure that we use `isValidRpId` to validate the rp.id + it("should throw if isValidRpId returns false", async () => { + const params = createParams(); + authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); + // `params` actually has a valid rp.id, but we're mocking the function to return false + isValidRpId.mockReturnValue(false); + + const result = async () => await client.createCredential(params, tab); + + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "SecurityError" }); + await rejects.toBeInstanceOf(DOMException); + }); + it("should fallback if origin hostname is found in neverDomains", async () => { const params = createParams({ origin: "https://bitwarden.com", @@ -151,6 +174,16 @@ describe("FidoAuthenticatorService", () => { await rejects.toBeInstanceOf(DOMException); }); + it("should not throw error if localhost is http", async () => { + const params = createParams({ + origin: "http://localhost", + rp: { id: undefined, name: "localhost" }, + }); + authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); + + await client.createCredential(params, tab); + }); + // Spec: If credTypesAndPubKeyAlgs is empty, return a DOMException whose name is "NotSupportedError", and terminate this algorithm. it("should throw error if no support key algorithms were found", async () => { const params = createParams({ @@ -360,6 +393,7 @@ describe("FidoAuthenticatorService", () => { }); // Spec: If options.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain, return a DOMException whose name is "SecurityError", and terminate this algorithm. + // This is actually checked by `isValidRpId` function, but we'll test it here as well it("should throw error if rp.id is not valid for this origin", async () => { const params = createParams({ origin: "https://passwordless.dev", @@ -373,6 +407,20 @@ describe("FidoAuthenticatorService", () => { await rejects.toBeInstanceOf(DOMException); }); + // Sanity check to make sure that we use `isValidRpId` to validate the rp.id + it("should throw if isValidRpId returns false", async () => { + const params = createParams(); + authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); + // `params` actually has a valid rp.id, but we're mocking the function to return false + isValidRpId.mockReturnValue(false); + + const result = async () => await client.assertCredential(params, tab); + + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "SecurityError" }); + await rejects.toBeInstanceOf(DOMException); + }); + it("should fallback if origin hostname is found in neverDomains", async () => { const params = createParams({ origin: "https://bitwarden.com", @@ -506,6 +554,16 @@ describe("FidoAuthenticatorService", () => { expect.anything(), ); }); + + it("should not throw error if localhost is http", async () => { + const params = createParams({ + origin: "http://localhost", + }); + params.rpId = undefined; + authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); + + await client.assertCredential(params, tab); + }); }); describe("assert discoverable credential", () => { diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index 5b6a13bc3f..d22b91fda0 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -103,7 +103,10 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { } params.rp.id = params.rp.id ?? parsedOrigin.hostname; - if (parsedOrigin.hostname == undefined || !params.origin.startsWith("https://")) { + if ( + parsedOrigin.hostname == undefined || + (!params.origin.startsWith("https://") && parsedOrigin.hostname !== "localhost") + ) { this.logService?.warning(`[Fido2Client] Invalid https origin: ${params.origin}`); throw new DOMException("'origin' is not a valid https origin", "SecurityError"); } @@ -238,7 +241,10 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { params.rpId = params.rpId ?? parsedOrigin.hostname; - if (parsedOrigin.hostname == undefined || !params.origin.startsWith("https://")) { + if ( + parsedOrigin.hostname == undefined || + (!params.origin.startsWith("https://") && parsedOrigin.hostname !== "localhost") + ) { this.logService?.warning(`[Fido2Client] Invalid https origin: ${params.origin}`); throw new DOMException("'origin' is not a valid https origin", "SecurityError"); } From 74f08f96f66924f21c32e61d5b40d60e8ae75a59 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 14:04:01 +0200 Subject: [PATCH 03/12] [deps] Tools: Update utf-8-validate to v6.0.4 (#9259) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c067a0b122..337525269c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "rxjs": "7.8.1", "tabbable": "6.2.0", "tldts": "6.1.29", - "utf-8-validate": "6.0.3", + "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" }, @@ -38757,10 +38757,11 @@ } }, "node_modules/utf-8-validate": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", - "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz", + "integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "node-gyp-build": "^4.3.0" }, diff --git a/package.json b/package.json index e30a00f580..56fcee7a57 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "rxjs": "7.8.1", "tabbable": "6.2.0", "tldts": "6.1.29", - "utf-8-validate": "6.0.3", + "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" }, From 0add2949c0694de9eacd381319540209e7b1f121 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 25 Jun 2024 08:21:02 -0400 Subject: [PATCH 04/12] [PM-8772] move ranges to static properties on class (#9567) * move ranges to static properties on class * rename static properties --- .../change-kdf/change-kdf.component.ts | 18 +++------ .../services/vault-banners.service.ts | 5 ++- .../src/auth/components/register.component.ts | 2 +- .../auth/components/set-password.component.ts | 3 +- .../src/auth/models/domain/kdf-config.ts | 39 ++++++++++--------- .../auth/services/kdf-config.service.spec.ts | 14 ++----- .../src/platform/enums/kdf-type.enum.ts | 11 ------ .../services/key-generation.service.ts | 18 +++------ .../individual-vault-export.service.spec.ts | 8 +++- .../src/services/vault-export.service.spec.ts | 8 +++- 10 files changed, 54 insertions(+), 72 deletions(-) diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index e9b35dd33d..58553ba198 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -5,17 +5,11 @@ import { Subject, takeUntil } from "rxjs"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { Argon2KdfConfig, + DEFAULT_KDF_CONFIG, KdfConfig, PBKDF2KdfConfig, } from "@bitwarden/common/auth/models/domain/kdf-config"; -import { - DEFAULT_KDF_CONFIG, - PBKDF2_ITERATIONS, - ARGON2_ITERATIONS, - ARGON2_MEMORY, - ARGON2_PARALLELISM, - KdfType, -} from "@bitwarden/common/platform/enums"; +import { KdfType } from "@bitwarden/common/platform/enums"; import { DialogService } from "@bitwarden/components"; import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.component"; @@ -56,10 +50,10 @@ export class ChangeKdfComponent implements OnInit { }); // Default values for template - protected PBKDF2_ITERATIONS = PBKDF2_ITERATIONS; - protected ARGON2_ITERATIONS = ARGON2_ITERATIONS; - protected ARGON2_MEMORY = ARGON2_MEMORY; - protected ARGON2_PARALLELISM = ARGON2_PARALLELISM; + protected PBKDF2_ITERATIONS = PBKDF2KdfConfig.ITERATIONS; + protected ARGON2_ITERATIONS = Argon2KdfConfig.ITERATIONS; + protected ARGON2_MEMORY = Argon2KdfConfig.MEMORY; + protected ARGON2_PARALLELISM = Argon2KdfConfig.PARALLELISM; constructor( private dialogService: DialogService, diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 1cb8e13cb3..172d81c48a 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -5,9 +5,10 @@ import { mergeMap, take } from "rxjs/operators"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums"; +import { KdfType } from "@bitwarden/common/platform/enums"; import { StateProvider, ActiveUserState, @@ -200,7 +201,7 @@ export class VaultBannersService { const kdfConfig = await this.kdfConfigService.getKdfConfig(); return ( kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && - kdfConfig.iterations < PBKDF2_ITERATIONS.defaultValue + kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue ); } diff --git a/libs/angular/src/auth/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts index e3197355dc..533920d13c 100644 --- a/libs/angular/src/auth/components/register.component.ts +++ b/libs/angular/src/auth/components/register.component.ts @@ -5,6 +5,7 @@ import { Router } from "@angular/router"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { RegisterResponse } from "@bitwarden/common/auth/models/response/register.response"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; @@ -15,7 +16,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { DialogService } from "@bitwarden/components"; diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 73c38c8ddb..b08c796753 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -17,6 +17,7 @@ import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -24,7 +25,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { HashPurpose, DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums"; +import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; diff --git a/libs/common/src/auth/models/domain/kdf-config.ts b/libs/common/src/auth/models/domain/kdf-config.ts index ce01f09702..7378081550 100644 --- a/libs/common/src/auth/models/domain/kdf-config.ts +++ b/libs/common/src/auth/models/domain/kdf-config.ts @@ -1,12 +1,7 @@ import { Jsonify } from "type-fest"; -import { - ARGON2_ITERATIONS, - ARGON2_MEMORY, - ARGON2_PARALLELISM, - KdfType, - PBKDF2_ITERATIONS, -} from "../../../platform/enums/kdf-type.enum"; +import { KdfType } from "../../../platform/enums/kdf-type.enum"; +import { RangeWithDefault } from "../../../platform/misc/range-with-default"; /** * Represents a type safe KDF configuration. @@ -17,11 +12,12 @@ export type KdfConfig = PBKDF2KdfConfig | Argon2KdfConfig; * Password-Based Key Derivation Function 2 (PBKDF2) KDF configuration. */ export class PBKDF2KdfConfig { + static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000); kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256; iterations: number; constructor(iterations?: number) { - this.iterations = iterations ?? PBKDF2_ITERATIONS.defaultValue; + this.iterations = iterations ?? PBKDF2KdfConfig.ITERATIONS.defaultValue; } /** @@ -29,9 +25,9 @@ export class PBKDF2KdfConfig { * A Valid PBKDF2 KDF configuration has KDF iterations between the 600_000 and 2_000_000. */ validateKdfConfig(): void { - if (!PBKDF2_ITERATIONS.inRange(this.iterations)) { + if (!PBKDF2KdfConfig.ITERATIONS.inRange(this.iterations)) { throw new Error( - `PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`, + `PBKDF2 iterations must be between ${PBKDF2KdfConfig.ITERATIONS.min} and ${PBKDF2KdfConfig.ITERATIONS.max}`, ); } } @@ -45,15 +41,18 @@ export class PBKDF2KdfConfig { * Argon2 KDF configuration. */ export class Argon2KdfConfig { + static MEMORY = new RangeWithDefault(16, 1024, 64); + static PARALLELISM = new RangeWithDefault(1, 16, 4); + static ITERATIONS = new RangeWithDefault(2, 10, 3); kdfType: KdfType.Argon2id = KdfType.Argon2id; iterations: number; memory: number; parallelism: number; constructor(iterations?: number, memory?: number, parallelism?: number) { - this.iterations = iterations ?? ARGON2_ITERATIONS.defaultValue; - this.memory = memory ?? ARGON2_MEMORY.defaultValue; - this.parallelism = parallelism ?? ARGON2_PARALLELISM.defaultValue; + this.iterations = iterations ?? Argon2KdfConfig.ITERATIONS.defaultValue; + this.memory = memory ?? Argon2KdfConfig.MEMORY.defaultValue; + this.parallelism = parallelism ?? Argon2KdfConfig.PARALLELISM.defaultValue; } /** @@ -61,21 +60,21 @@ export class Argon2KdfConfig { * A Valid Argon2 KDF configuration has iterations between 2 and 10, memory between 16mb and 1024mb, and parallelism between 1 and 16. */ validateKdfConfig(): void { - if (!ARGON2_ITERATIONS.inRange(this.iterations)) { + if (!Argon2KdfConfig.ITERATIONS.inRange(this.iterations)) { throw new Error( - `Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`, + `Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`, ); } - if (!ARGON2_MEMORY.inRange(this.memory)) { + if (!Argon2KdfConfig.MEMORY.inRange(this.memory)) { throw new Error( - `Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`, + `Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`, ); } - if (!ARGON2_PARALLELISM.inRange(this.parallelism)) { + if (!Argon2KdfConfig.PARALLELISM.inRange(this.parallelism)) { throw new Error( - `Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}.`, + `Argon2 parallelism must be between ${Argon2KdfConfig.PARALLELISM.min} and ${Argon2KdfConfig.PARALLELISM.max}.`, ); } } @@ -84,3 +83,5 @@ export class Argon2KdfConfig { return new Argon2KdfConfig(json.iterations, json.memory, json.parallelism); } } + +export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2KdfConfig.ITERATIONS.defaultValue); diff --git a/libs/common/src/auth/services/kdf-config.service.spec.ts b/libs/common/src/auth/services/kdf-config.service.spec.ts index 67bcf721bc..7f8357ffb5 100644 --- a/libs/common/src/auth/services/kdf-config.service.spec.ts +++ b/libs/common/src/auth/services/kdf-config.service.spec.ts @@ -1,10 +1,4 @@ import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; -import { - ARGON2_ITERATIONS, - ARGON2_MEMORY, - ARGON2_PARALLELISM, - PBKDF2_ITERATIONS, -} from "../../platform/enums/kdf-type.enum"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; import { Argon2KdfConfig, PBKDF2KdfConfig } from "../models/domain/kdf-config"; @@ -77,28 +71,28 @@ describe("KdfConfigService", () => { it("validateKdfConfig(): should throw an error for invalid PBKDF2 iterations", () => { const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(100); expect(() => kdfConfig.validateKdfConfig()).toThrow( - `PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`, + `PBKDF2 iterations must be between ${PBKDF2KdfConfig.ITERATIONS.min} and ${PBKDF2KdfConfig.ITERATIONS.max}`, ); }); it("validateKdfConfig(): should throw an error for invalid Argon2 iterations", () => { const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(11, 64, 4); expect(() => kdfConfig.validateKdfConfig()).toThrow( - `Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`, + `Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`, ); }); it("validateKdfConfig(): should throw an error for invalid Argon2 memory", () => { const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 1025, 4); expect(() => kdfConfig.validateKdfConfig()).toThrow( - `Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`, + `Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`, ); }); it("validateKdfConfig(): should throw an error for invalid Argon2 parallelism", () => { const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17); expect(() => kdfConfig.validateKdfConfig()).toThrow( - `Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}`, + `Argon2 parallelism must be between ${Argon2KdfConfig.PARALLELISM.min} and ${Argon2KdfConfig.PARALLELISM.max}`, ); }); }); diff --git a/libs/common/src/platform/enums/kdf-type.enum.ts b/libs/common/src/platform/enums/kdf-type.enum.ts index fd29bf308c..29fcd9f1f8 100644 --- a/libs/common/src/platform/enums/kdf-type.enum.ts +++ b/libs/common/src/platform/enums/kdf-type.enum.ts @@ -1,15 +1,4 @@ -import { PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config"; -import { RangeWithDefault } from "../misc/range-with-default"; - export enum KdfType { PBKDF2_SHA256 = 0, Argon2id = 1, } - -export const ARGON2_MEMORY = new RangeWithDefault(16, 1024, 64); -export const ARGON2_PARALLELISM = new RangeWithDefault(1, 16, 4); -export const ARGON2_ITERATIONS = new RangeWithDefault(2, 10, 3); - -export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256; -export const PBKDF2_ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000); -export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2_ITERATIONS.defaultValue); diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index 2a25ffde2f..b1c1ddfcf1 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,14 +1,8 @@ -import { KdfConfig } from "../../auth/models/domain/kdf-config"; +import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config"; import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "../abstractions/key-generation.service"; -import { - ARGON2_ITERATIONS, - ARGON2_MEMORY, - ARGON2_PARALLELISM, - KdfType, - PBKDF2_ITERATIONS, -} from "../enums"; +import { KdfType } from "../enums"; import { Utils } from "../misc/utils"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; @@ -51,21 +45,21 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction { let key: Uint8Array = null; if (kdfConfig.kdfType == null || kdfConfig.kdfType === KdfType.PBKDF2_SHA256) { if (kdfConfig.iterations == null) { - kdfConfig.iterations = PBKDF2_ITERATIONS.defaultValue; + kdfConfig.iterations = PBKDF2KdfConfig.ITERATIONS.defaultValue; } key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations); } else if (kdfConfig.kdfType == KdfType.Argon2id) { if (kdfConfig.iterations == null) { - kdfConfig.iterations = ARGON2_ITERATIONS.defaultValue; + kdfConfig.iterations = Argon2KdfConfig.ITERATIONS.defaultValue; } if (kdfConfig.memory == null) { - kdfConfig.memory = ARGON2_MEMORY.defaultValue; + kdfConfig.memory = Argon2KdfConfig.MEMORY.defaultValue; } if (kdfConfig.parallelism == null) { - kdfConfig.parallelism = ARGON2_PARALLELISM.defaultValue; + kdfConfig.parallelism = Argon2KdfConfig.PARALLELISM.defaultValue; } const saltHash = await this.cryptoFunctionService.hash(salt, "sha256"); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 9b5c4d8bf5..44df18116d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -2,10 +2,14 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; +import { + DEFAULT_KDF_CONFIG, + PBKDF2KdfConfig, +} from "@bitwarden/common/auth/models/domain/kdf-config"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { DEFAULT_KDF_CONFIG, KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums"; +import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -238,7 +242,7 @@ describe("VaultExportService", () => { }); it("specifies kdfIterations", () => { - expect(exportObject.kdfIterations).toEqual(PBKDF2_ITERATIONS.defaultValue); + expect(exportObject.kdfIterations).toEqual(PBKDF2KdfConfig.ITERATIONS.defaultValue); }); it("has kdfType", () => { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts index 9b5c4d8bf5..44df18116d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts @@ -2,10 +2,14 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; +import { + DEFAULT_KDF_CONFIG, + PBKDF2KdfConfig, +} from "@bitwarden/common/auth/models/domain/kdf-config"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { DEFAULT_KDF_CONFIG, KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums"; +import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -238,7 +242,7 @@ describe("VaultExportService", () => { }); it("specifies kdfIterations", () => { - expect(exportObject.kdfIterations).toEqual(PBKDF2_ITERATIONS.defaultValue); + expect(exportObject.kdfIterations).toEqual(PBKDF2KdfConfig.ITERATIONS.defaultValue); }); it("has kdfType", () => { From 1fdfd695e148dbd9ff84038a602edeae179f4fc7 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 25 Jun 2024 08:57:59 -0400 Subject: [PATCH 05/12] reference correct default kdf config (#9822) * reference correct default kdf config * update change kdf references --- .../change-kdf/change-kdf.component.ts | 30 +++++++++++-------- .../input-password.component.ts | 6 ++-- .../common/services/pin/pin.service.spec.ts | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index 58553ba198..2a615ef397 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -30,20 +30,24 @@ export class ChangeKdfComponent implements OnInit { this.kdfConfig.iterations, [ Validators.required, - Validators.min(PBKDF2_ITERATIONS.min), - Validators.max(PBKDF2_ITERATIONS.max), + Validators.min(PBKDF2KdfConfig.ITERATIONS.min), + Validators.max(PBKDF2KdfConfig.ITERATIONS.max), ], ], memory: [ null as number, - [Validators.required, Validators.min(ARGON2_MEMORY.min), Validators.max(ARGON2_MEMORY.max)], + [ + Validators.required, + Validators.min(Argon2KdfConfig.MEMORY.min), + Validators.max(Argon2KdfConfig.MEMORY.max), + ], ], parallelism: [ null as number, [ Validators.required, - Validators.min(ARGON2_PARALLELISM.min), - Validators.max(ARGON2_PARALLELISM.max), + Validators.min(Argon2KdfConfig.PARALLELISM.min), + Validators.max(Argon2KdfConfig.PARALLELISM.max), ], ], }), @@ -91,26 +95,26 @@ export class ChangeKdfComponent implements OnInit { config = new PBKDF2KdfConfig(); validators.iterations = [ Validators.required, - Validators.min(PBKDF2_ITERATIONS.min), - Validators.max(PBKDF2_ITERATIONS.max), + Validators.min(PBKDF2KdfConfig.ITERATIONS.min), + Validators.max(PBKDF2KdfConfig.ITERATIONS.max), ]; break; case KdfType.Argon2id: config = new Argon2KdfConfig(); validators.iterations = [ Validators.required, - Validators.min(ARGON2_ITERATIONS.min), - Validators.max(ARGON2_ITERATIONS.max), + Validators.min(Argon2KdfConfig.ITERATIONS.min), + Validators.max(Argon2KdfConfig.ITERATIONS.max), ]; validators.memory = [ Validators.required, - Validators.min(ARGON2_MEMORY.min), - Validators.max(ARGON2_MEMORY.max), + Validators.min(Argon2KdfConfig.MEMORY.min), + Validators.max(Argon2KdfConfig.MEMORY.max), ]; validators.parallelism = [ Validators.required, - Validators.min(ARGON2_PARALLELISM.min), - Validators.max(ARGON2_PARALLELISM.max), + Validators.min(Argon2KdfConfig.PARALLELISM.min), + Validators.max(Argon2KdfConfig.PARALLELISM.max), ]; break; default: diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index cc91e2a255..49d02361d4 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -6,10 +6,12 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; +import { + DEFAULT_KDF_CONFIG, + PBKDF2KdfConfig, +} from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { MasterKey } from "@bitwarden/common/types/key"; import { diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index 834e581dc6..b40d37d424 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -1,13 +1,13 @@ import { mock } from "jest-mock-extended"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; +import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; From a0e00f9519da0c758a33cf8f848ae874ac202d88 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:54:27 -0400 Subject: [PATCH 06/12] [deps] Platform: Update @types/node to v20.14.8 (#9818) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index ac12731398..0e3614f078 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.14.1", + "@types/node": "20.14.8", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -98,9 +98,9 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "node_modules/@types/node": { - "version": "20.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", - "integrity": "sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==", + "version": "20.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", + "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 0f92d5b0b3..9579659137 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.14.1", + "@types/node": "20.14.8", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index 337525269c..9b24c737fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -110,7 +110,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.14.1", + "@types/node": "20.14.8", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -11750,9 +11750,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", - "integrity": "sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==", + "version": "20.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", + "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 56fcee7a57..70c3803aba 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.14.1", + "@types/node": "20.14.8", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", From b6c46745a57873a2497de161e36f51436841e4a7 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:45:21 -0400 Subject: [PATCH 07/12] Add default OrganizationUpdateRequest values when FormControls are disabled (#9774) --- .../organizations/settings/account.component.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 5e083de9cc..82f9a24993 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -153,8 +153,17 @@ export class AccountComponent { } const request = new OrganizationUpdateRequest(); - request.name = this.formGroup.value.orgName; - request.billingEmail = this.formGroup.value.billingEmail; + + /* + * When you disable a FormControl, it is removed from formGroup.values, so we have to use + * the original value. + * */ + request.name = this.formGroup.get("orgName").disabled + ? this.org.name + : this.formGroup.value.orgName; + request.billingEmail = this.formGroup.get("billingEmail").disabled + ? this.org.billingEmail + : this.formGroup.value.billingEmail; // Backfill pub/priv key if necessary if (!this.org.hasPublicAndPrivateKeys) { From 7fffbc7938ad0ff7cdb46e7487390a6d021d1cfe Mon Sep 17 00:00:00 2001 From: KiruthigaManivannan <162679756+KiruthigaManivannan@users.noreply.github.com> Date: Tue, 25 Jun 2024 20:33:48 +0530 Subject: [PATCH 08/12] PM- 2059 Update Two factor webauthn dialog (#9009) * PM-2059 Update Two Factor Webauth Dialog * PM-2059 Added event emitter for enabled status * PM-2059 Addressed review comments * convert to arrow function * PM-2059 Latest comments addressed * PM-2059 Updated disable method by adding a condition to capture simple dialog in base component --------- Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> --- .../settings/two-factor-setup.component.ts | 11 +- .../two-factor-webauthn.component.html | 256 ++++++++---------- .../settings/two-factor-webauthn.component.ts | 57 +++- 3 files changed, 157 insertions(+), 167 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor-setup.component.ts index b1592dc72a..10f113d496 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.ts @@ -39,8 +39,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { @ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef; @ViewChild("emailTemplate", { read: ViewContainerRef, static: true }) emailModalRef: ViewContainerRef; - @ViewChild("webAuthnTemplate", { read: ViewContainerRef, static: true }) - webAuthnModalRef: ViewContainerRef; organizationId: string; organization: Organization; @@ -192,12 +190,11 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const webAuthnComp = await this.openModal( - this.webAuthnModalRef, - TwoFactorWebAuthnComponent, + const webAuthnComp: DialogRef = TwoFactorWebAuthnComponent.open( + this.dialogService, + { data: result }, ); - webAuthnComp.auth(result); - webAuthnComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => { + webAuthnComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => { this.updateStatus(enabled, TwoFactorProviderType.WebAuthn); }); break; diff --git a/apps/web/src/app/auth/settings/two-factor-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor-webauthn.component.html index f5a84397c8..9dc9bd4068 100644 --- a/apps/web/src/app/auth/settings/two-factor-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor-webauthn.component.html @@ -1,152 +1,118 @@ - + - + {{ "remove" | i18n }} + + + +
+

{{ "twoFactorWebAuthnAdd" | i18n }}:

+
    +
  1. {{ "twoFactorU2fGiveName" | i18n }}
  2. +
  3. {{ "twoFactorU2fPlugInReadKey" | i18n }}
  4. +
  5. {{ "twoFactorU2fTouchButton" | i18n }}
  6. +
  7. {{ "twoFactorU2fSaveForm" | i18n }}
  8. +
+
+ + {{ "name" | i18n }} + + +
+ + + + + + + + {{ "twoFactorU2fWaiting" | i18n }}... + + + + {{ "twoFactorU2fClickSave" | i18n }} + + + + {{ "twoFactorU2fProblemReadingTryAgain" | i18n }} + + + + + + + + + + diff --git a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts index fc2de26721..5e8ea37e93 100644 --- a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts @@ -1,4 +1,6 @@ -import { Component, NgZone } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, NgZone, Output } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -31,6 +33,7 @@ interface Key { templateUrl: "two-factor-webauthn.component.html", }) export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { + @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.WebAuthn; name: string; keys: Key[]; @@ -44,7 +47,13 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { override componentName = "app-two-factor-webauthn"; + protected formGroup = new FormGroup({ + name: new FormControl({ value: "", disabled: !this.keyIdAvailable }), + }); + constructor( + @Inject(DIALOG_DATA) protected data: AuthResponse, + private dialogRef: DialogRef, apiService: ApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, @@ -61,6 +70,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { userVerificationService, dialogService, ); + this.auth(data); } auth(authResponse: AuthResponse) { @@ -68,7 +78,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { this.processResponse(authResponse.response); } - async submit() { + submit = async () => { if (this.webAuthnResponse == null || this.keyIdAvailable == null) { // Should never happen. return Promise.reject(); @@ -76,16 +86,28 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest); request.deviceResponse = this.webAuthnResponse; request.id = this.keyIdAvailable; - request.name = this.name; + request.name = this.formGroup.value.name; + return this.enableWebAuth(request); + }; + + private enableWebAuth(request: any) { return super.enable(async () => { this.formPromise = this.apiService.putTwoFactorWebAuthn(request); const response = await this.formPromise; - await this.processResponse(response); + this.processResponse(response); }); } - disable() { + disable = async () => { + await this.disableWebAuth(); + if (!this.enabled) { + this.onChangeStatus.emit(this.enabled); + this.dialogRef.close(); + } + }; + + private async disableWebAuth() { return super.disable(this.formPromise); } @@ -116,19 +138,15 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { } } - async readKey() { + readKey = async () => { if (this.keyIdAvailable == null) { return; } const request = await this.buildRequestModel(SecretVerificationRequest); - try { - this.challengePromise = this.apiService.getTwoFactorWebAuthnChallenge(request); - const challenge = await this.challengePromise; - this.readDevice(challenge); - } catch (e) { - this.logService.error(e); - } - } + this.challengePromise = this.apiService.getTwoFactorWebAuthnChallenge(request); + const challenge = await this.challengePromise; + this.readDevice(challenge); + }; private readDevice(webAuthnChallenge: ChallengeResponse) { // eslint-disable-next-line @@ -164,7 +182,8 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { this.resetWebAuthn(); this.keys = []; this.keyIdAvailable = null; - this.name = null; + this.formGroup.get("name").enable(); + this.formGroup.get("name").setValue(null); this.keysConfiguredCount = 0; for (let i = 1; i <= 5; i++) { if (response.keys != null) { @@ -187,5 +206,13 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { } } this.enabled = response.enabled; + this.onChangeStatus.emit(this.enabled); + } + + static open( + dialogService: DialogService, + config: DialogConfig>, + ) { + return dialogService.open(TwoFactorWebAuthnComponent, config); } } From 44fb8cda1bcfa95175e4e6a0b000d2773fc82984 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:00:31 -0400 Subject: [PATCH 09/12] Bumped client version(s) (#9826) --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 749726b1d6..697ab88ae9 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2024.6.5", + "version": "2024.6.6", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 5ea4ac88e9..1463087bd3 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.6.5", + "version": "2024.6.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.6.5", + "version": "2024.6.6", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 19b2d63d25..2030c29189 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2024.6.5", + "version": "2024.6.6", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 9b24c737fb..78ed8111e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -237,7 +237,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.6.5", + "version": "2024.6.6", "hasInstallScript": true, "license": "GPL-3.0" }, From 9ec01422dfe7136e177aee2d224c41dc734e3384 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:04:49 -0700 Subject: [PATCH 10/12] [PM-5085] Add additional InputPasswordComponent story and docs (#9752) * split stories: 1 default, 1 with policy * add storybook docs --- .../angular/input-password/input-password.mdx | 75 +++++++++++++++++++ .../input-password/input-password.stories.ts | 30 ++++++-- 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 libs/auth/src/angular/input-password/input-password.mdx diff --git a/libs/auth/src/angular/input-password/input-password.mdx b/libs/auth/src/angular/input-password/input-password.mdx new file mode 100644 index 0000000000..d442ea02fc --- /dev/null +++ b/libs/auth/src/angular/input-password/input-password.mdx @@ -0,0 +1,75 @@ +import { Meta, Story } from "@storybook/addon-docs"; + +import * as stories from "./input-password.stories.ts"; + + + +# InputPassword Component + +The `InputPasswordComponent` allows a user to enter a master password and hint. On submission it +creates a master key, master key hash, and emits those values to the parent (along with the hint and +default kdfConfig). + +The component is intended for re-use in different scenarios throughout the application. Therefore it +is mostly presentational and simply emits values rather than acting on them itself. It is the job of +the parent component to act on those values as needed. + +
+ +## `@Input()`'s + +- `email` (**required**) - the parent component must provide an email so that the + `InputPasswordComponent` can create a master key. +- `buttonText` (optional) - an `i18n` translated string that can be used as button text (default + text is "Set master password"). +- `orgId` (optional) - used to retreive and enforce the master password policy requirements for an + org. + +
+ +## Form Input Fields + +The `InputPasswordComponent` allows a user to enter: + +1. Master password +2. Master password confirmation +3. Hint (optional) +4. Chooses whether to check for password breaches (checkbox) + +Validation ensures that the master password and confirmed master password are the same, and that the +master password and hint values are not the same. + +
+ +## On Submit + +When the form is submitted, the `InputPasswordComponent` does the following in order: + +1. If the user selected the checkbox to check for password breaches, they will recieve a popup + dialog if their entered password is found in a breach. The popup will give them the option to + continue with the password or to back out and choose a different password. +2. If there is a master password policy being enforced by an org, it will check to make sure the + entered master password meets the policy requirements. +3. The component will use the password, email, and default kdfConfig to create a master key and + master key hash. +4. The component will emit the following values (defined in the `PasswordInputResult` interface) to + be used by the parent component as needed: + +```typescript +export interface PasswordInputResult { + masterKey: MasterKey; + masterKeyHash: string; + kdfConfig: PBKDF2KdfConfig; + hint: string; +} +``` + +# Default Example + + + +
+ +# With Policy Requrements + + diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 6144e39e64..a0dee87b27 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -29,7 +29,10 @@ const mockMasterPasswordPolicyOptions = { export default { title: "Auth/Input Password", component: InputPasswordComponent, - decorators: [ +} as Meta; + +const decorators = (options: { hasPolicy?: boolean }) => { + return [ applicationConfig({ providers: [ importProvidersFrom(PreloadedEnglishI18nModule), @@ -56,13 +59,15 @@ export default { { provide: PolicyApiServiceAbstraction, useValue: { - getMasterPasswordPolicyOptsForOrgUser: () => mockMasterPasswordPolicyOptions, + getMasterPasswordPolicyOptsForOrgUser: () => + options.hasPolicy ? mockMasterPasswordPolicyOptions : null, } as Partial, }, { provide: PolicyService, useValue: { - masterPasswordPolicyOptions$: () => of(mockMasterPasswordPolicyOptions), + masterPasswordPolicyOptions$: () => + options.hasPolicy ? of(mockMasterPasswordPolicyOptions) : null, evaluateMasterPassword: (score) => { if (score < 4) { return false; @@ -101,8 +106,8 @@ export default { }, ], }), - ], -} as Meta; + ]; +}; type Story = StoryObj; @@ -113,4 +118,19 @@ export const Default: Story = { `, }), + decorators: decorators({ + hasPolicy: false, + }), +}; + +export const WithPolicy: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + hasPolicy: true, + }), }; From c35bbc522c5a51d4d4ade58ce24e5b59517b2b29 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:17:03 +0200 Subject: [PATCH 11/12] [PM-9051][PM-8641] Use reusable export-component on web (#9741) * Add export-web.component Introduce new export-web component Delete old component export.module - With export-web being standalone there's no need for a importModule Change routing to load new component * Prepare export.component to receive a orgId via the hosting-component * Remove unused onSaved as it's replaced by onSuccessfulExport * Refactor org-vault-export.component Introduce new org-vault-export.component.html as the old component relied on the markup from password manager Refactor org-vault-export.component Retrieve organizationId from Route and pass it into the shared export.component Ensure when exporting from AC to include all data from the selected org org-vault-export.module - With the new component being standalone there's no need for a importModule Change routing to load new org-vault-export component * PM-8641 - Add success toast to base-export component This ensures a success toast is shown on all clients consistently Add missing entries into clients messages.json --------- Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 3 + apps/desktop/src/locales/en/messages.json | 3 + .../organization-settings-routing.module.ts | 8 +- .../org-vault-export-routing.module.ts | 25 ---- .../org-vault-export.component.html | 21 ++++ .../org-vault-export.component.ts | 87 +++---------- .../vault-export/org-vault-export.module.ts | 19 --- apps/web/src/app/oss-routing.module.ts | 9 +- .../vault-export/export-routing.module.ts | 17 --- .../vault-export/export-web.component.html | 20 +++ .../vault-export/export-web.component.ts | 24 ++++ .../tools/vault-export/export.component.html | 115 ------------------ .../tools/vault-export/export.component.ts | 53 -------- .../app/tools/vault-export/export.module.ts | 14 --- .../src/components/export.component.ts | 49 ++++++-- 15 files changed, 138 insertions(+), 329 deletions(-) delete mode 100644 apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts create mode 100644 apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html delete mode 100644 apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts delete mode 100644 apps/web/src/app/tools/vault-export/export-routing.module.ts create mode 100644 apps/web/src/app/tools/vault-export/export-web.component.html create mode 100644 apps/web/src/app/tools/vault-export/export-web.component.ts delete mode 100644 apps/web/src/app/tools/vault-export/export.component.html delete mode 100644 apps/web/src/app/tools/vault-export/export.component.ts delete mode 100644 apps/web/src/app/tools/vault-export/export.module.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7a0c059a5c..64f039bb8b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3107,6 +3107,9 @@ "confirmFilePassword": { "message": "Confirm file password" }, + "exportSuccess": { + "message": "Vault data exported" + }, "typePasskey": { "message": "Passkey" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 7c72ea58fb..333a1c0e7b 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2843,6 +2843,9 @@ "confirmFilePassword": { "message": "Confirm file password" }, + "exportSuccess": { + "message": "Vault data exported" + }, "multifactorAuthenticationCancelled": { "message": "Multifactor authentication cancelled" }, 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 772e70fc12..cc65bef8c7 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 @@ -56,12 +56,14 @@ const routes: Routes = [ }, { path: "export", - loadChildren: () => - import("../tools/vault-export/org-vault-export.module").then( - (m) => m.OrganizationVaultExportModule, + loadComponent: () => + import("../tools/vault-export/org-vault-export.component").then( + (mod) => mod.OrganizationVaultExportComponent, ), + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exportVault", + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, ], diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts deleted file mode 100644 index e3e809a550..0000000000 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-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 { OrganizationVaultExportComponent } from "./org-vault-export.component"; - -const routes: Routes = [ - { - path: "", - component: OrganizationVaultExportComponent, - canActivate: [OrganizationPermissionsGuard], - data: { - titleId: "exportVault", - organizationPermissions: (org: Organization) => org.canAccessImportExport, - }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class OrganizationVaultExportRoutingModule {} diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html new file mode 100644 index 0000000000..01975272e7 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts index 0caf39ea79..16e9f76d21 100644 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts @@ -1,83 +1,28 @@ -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { EventType } from "@bitwarden/common/enums"; -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 { DialogService, ToastService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; +import { ExportComponent } from "@bitwarden/vault-export-ui"; -import { ExportComponent } from "../../../../tools/vault-export/export.component"; +import { LooseComponentsModule, SharedModule } from "../../../../shared"; @Component({ - selector: "app-org-export", - templateUrl: "../../../../tools/vault-export/export.component.html", + templateUrl: "org-vault-export.component.html", + standalone: true, + imports: [SharedModule, ExportComponent, LooseComponentsModule], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class OrganizationVaultExportComponent extends ExportComponent { - constructor( - i18nService: I18nService, - toastService: ToastService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - private route: ActivatedRoute, - policyService: PolicyService, - logService: LogService, - formBuilder: UntypedFormBuilder, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - toastService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } +export class OrganizationVaultExportComponent implements OnInit { + protected routeOrgId: string = null; + protected loading = false; + protected disabled = false; - protected get disabledByPolicy(): boolean { - return false; - } + constructor(private route: ActivatedRoute) {} async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organizationId = params.organizationId; - }); - - await super.ngOnInit(); + this.routeOrgId = this.route.snapshot.paramMap.get("organizationId"); } - getExportData() { - return this.exportService.getOrganizationExport( - this.organizationId, - this.format, - this.filePassword, - ); - } - - getFileName() { - return super.getFileName("org"); - } - - async collectEvent(): Promise { - await this.eventCollectionService.collect( - EventType.Organization_ClientExportedVault, - null, - null, - this.organizationId, - ); - } + /** + * Callback that is called after a successful export. + */ + protected async onSuccessfulExport(organizationId: string): Promise {} } diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts deleted file mode 100644 index ca8a75165b..0000000000 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; - -import { LooseComponentsModule, SharedModule } from "../../../../shared"; - -import { OrganizationVaultExportRoutingModule } from "./org-vault-export-routing.module"; -import { OrganizationVaultExportComponent } from "./org-vault-export.component"; - -@NgModule({ - imports: [ - SharedModule, - LooseComponentsModule, - OrganizationVaultExportRoutingModule, - ExportScopeCalloutComponent, - ], - declarations: [OrganizationVaultExportComponent], -}) -export class OrganizationVaultExportModule {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index e7236348a6..dee6228530 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -448,8 +448,13 @@ const routes: Routes = [ }, { path: "export", - loadChildren: () => - import("./tools/vault-export/export.module").then((m) => m.ExportModule), + loadComponent: () => + import("./tools/vault-export/export-web.component").then( + (mod) => mod.ExportWebComponent, + ), + data: { + titleId: "exportVault", + } satisfies DataProperties, }, { path: "generator", diff --git a/apps/web/src/app/tools/vault-export/export-routing.module.ts b/apps/web/src/app/tools/vault-export/export-routing.module.ts deleted file mode 100644 index 3afda4a06f..0000000000 --- a/apps/web/src/app/tools/vault-export/export-routing.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { ExportComponent } from "./export.component"; - -const routes: Routes = [ - { - path: "", - component: ExportComponent, - data: { titleId: "exportVault" }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class ExportRoutingModule {} diff --git a/apps/web/src/app/tools/vault-export/export-web.component.html b/apps/web/src/app/tools/vault-export/export-web.component.html new file mode 100644 index 0000000000..e3d0ca75d2 --- /dev/null +++ b/apps/web/src/app/tools/vault-export/export-web.component.html @@ -0,0 +1,20 @@ + + + + + + diff --git a/apps/web/src/app/tools/vault-export/export-web.component.ts b/apps/web/src/app/tools/vault-export/export-web.component.ts new file mode 100644 index 0000000000..f2612656ce --- /dev/null +++ b/apps/web/src/app/tools/vault-export/export-web.component.ts @@ -0,0 +1,24 @@ +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; + +import { ExportComponent } from "@bitwarden/vault-export-ui"; + +import { HeaderModule } from "../../layouts/header/header.module"; +import { SharedModule } from "../../shared"; + +@Component({ + templateUrl: "export-web.component.html", + standalone: true, + imports: [SharedModule, ExportComponent, HeaderModule], +}) +export class ExportWebComponent { + protected loading = false; + protected disabled = false; + + constructor(private router: Router) {} + + /** + * Callback that is called after a successful export. + */ + protected async onSuccessfulExport(organizationId: string): Promise {} +} diff --git a/apps/web/src/app/tools/vault-export/export.component.html b/apps/web/src/app/tools/vault-export/export.component.html deleted file mode 100644 index 9f47adf8aa..0000000000 --- a/apps/web/src/app/tools/vault-export/export.component.html +++ /dev/null @@ -1,115 +0,0 @@ - - - -
- - {{ "personalVaultExportPolicyInEffect" | i18n }} - - - - - - {{ "exportFrom" | i18n }} - - - - - - - - - {{ "fileFormat" | i18n }} - - - - - - - - {{ "exportTypeHeading" | i18n }} - - - {{ "accountRestricted" | i18n }} - {{ "accountRestrictedOptionDescription" | i18n }} - - - - {{ "passwordProtected" | i18n }} - {{ "passwordProtectedOptionDescription" | i18n }} - - - - -
- - {{ "filePassword" | i18n }} - - - {{ "exportPasswordDescription" | i18n }} - - - -
- - {{ "confirmFilePassword" | i18n }} - - - -
-
- - -
-
diff --git a/apps/web/src/app/tools/vault-export/export.component.ts b/apps/web/src/app/tools/vault-export/export.component.ts deleted file mode 100644 index 8b5f82167d..0000000000 --- a/apps/web/src/app/tools/vault-export/export.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; - -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { 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 { DialogService, ToastService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; -import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui"; - -@Component({ - selector: "app-export", - templateUrl: "export.component.html", -}) -export class ExportComponent extends BaseExportComponent { - constructor( - i18nService: I18nService, - toastService: ToastService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - logService: LogService, - formBuilder: UntypedFormBuilder, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - toastService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } - - protected saved() { - super.saved(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("exportSuccess"), - }); - } -} diff --git a/apps/web/src/app/tools/vault-export/export.module.ts b/apps/web/src/app/tools/vault-export/export.module.ts deleted file mode 100644 index ddf82b0a10..0000000000 --- a/apps/web/src/app/tools/vault-export/export.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; - -import { LooseComponentsModule, SharedModule } from "../../shared"; - -import { ExportRoutingModule } from "./export-routing.module"; -import { ExportComponent } from "./export.component"; - -@NgModule({ - imports: [SharedModule, LooseComponentsModule, ExportRoutingModule, ExportScopeCalloutComponent], - declarations: [ExportComponent], -}) -export class ExportModule {} diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 9f81f5e550..baa463d913 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -1,5 +1,13 @@ import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms"; import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs"; @@ -53,6 +61,26 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component"; ], }) export class ExportComponent implements OnInit, OnDestroy { + private _organizationId: string; + + get organizationId(): string { + return this._organizationId; + } + + /** + * Enables the hosting control to pass in an organizationId + * If a organizationId is provided, the organization selection is disabled. + */ + @Input() set organizationId(value: string) { + this._organizationId = value; + this.organizationService + .get$(this._organizationId) + .pipe(takeUntil(this.destroy$)) + .subscribe((organization) => { + this._organizationId = organization?.id; + }); + } + /** * The hosting control also needs a bitSubmitDirective (on the Submit button) which calls this components {@link submit}-method. * This components formState (loading/disabled) is emitted back up to the hosting component so for example the Submit button can be enabled/disabled and show loading state. @@ -82,7 +110,6 @@ export class ExportComponent implements OnInit, OnDestroy { @Output() onSuccessfulExport = new EventEmitter(); - @Output() onSaved = new EventEmitter(); @ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent; encryptedExportType = EncryptedExportType; @@ -91,7 +118,6 @@ export class ExportComponent implements OnInit, OnDestroy { filePasswordValue: string = null; private _disabledByPolicy = false; - protected organizationId: string = null; organizations$: Observable; protected get disabledByPolicy(): boolean { @@ -120,6 +146,7 @@ export class ExportComponent implements OnInit, OnDestroy { ]; private destroy$ = new Subject(); + private onlyManagedCollections = true; constructor( protected i18nService: I18nService, @@ -163,6 +190,8 @@ export class ExportComponent implements OnInit, OnDestroy { ); this.exportForm.controls.vaultSelector.patchValue(this.organizationId); this.exportForm.controls.vaultSelector.disable(); + + this.onlyManagedCollections = false; return; } @@ -211,7 +240,12 @@ export class ExportComponent implements OnInit, OnDestroy { try { const data = await this.getExportData(); this.downloadFile(data); - this.saved(); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("exportSuccess"), + }); + this.onSuccessfulExport.emit(this.organizationId); await this.collectEvent(); this.exportForm.get("secret").setValue(""); this.exportForm.clearValidators(); @@ -252,11 +286,6 @@ export class ExportComponent implements OnInit, OnDestroy { await this.doExport(); }; - protected saved() { - this.onSaved.emit(); - this.onSuccessfulExport.emit(this.organizationId); - } - private async verifyUser(): Promise { let confirmDescription = "exportWarningDesc"; if (this.isFileEncryptedExport) { @@ -298,7 +327,7 @@ export class ExportComponent implements OnInit, OnDestroy { this.organizationId, this.format, this.filePassword, - true, + this.onlyManagedCollections, ); } From 41e1d9155850581be85d11e4b74088aa022f8381 Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:09:45 -0700 Subject: [PATCH 12/12] [PM-5156] [PM-5216] Duo v2 removal (#9513) * remove library and update package and webpack * update 2fa flow and remove feature flag * update request and response models * fix merge conflicts --- .github/renovate.json | 9 +- .../src/auth/popup/two-factor.component.html | 15 +- apps/browser/src/auth/scripts/duo.js | 418 ------------------ .../src/auth/two-factor.component.html | 25 +- .../auth/settings/two-factor-duo.component.ts | 8 +- .../src/app/auth/two-factor.component.html | 29 +- apps/web/src/connectors/duo.html | 17 - apps/web/src/connectors/duo.scss | 18 - apps/web/src/connectors/duo.ts | 47 -- apps/web/webpack.config.js | 6 - .../auth/components/two-factor.component.ts | 44 +- .../request/update-two-factor-duo.request.ts | 4 +- .../response/two-factor-duo.response.ts | 8 +- package-lock.json | 13 - package.json | 2 - 15 files changed, 36 insertions(+), 627 deletions(-) delete mode 100644 apps/browser/src/auth/scripts/duo.js delete mode 100644 apps/web/src/connectors/duo.html delete mode 100644 apps/web/src/connectors/duo.scss delete mode 100644 apps/web/src/connectors/duo.ts diff --git a/.github/renovate.json b/.github/renovate.json index 95fd2dc11e..e202e02667 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -69,14 +69,7 @@ "reviewers": ["team:team-admin-console-dev"] }, { - "matchPackageNames": [ - "@types/duo_web_sdk", - "@types/node-ipc", - "duo_web_sdk", - "node-ipc", - "qrious", - "regedit" - ], + "matchPackageNames": ["@types/node-ipc", "node-ipc", "qrious", "regedit"], "description": "Auth owned dependencies", "commitMessagePrefix": "[deps] Auth:", "reviewers": ["team:team-auth-dev"] diff --git a/apps/browser/src/auth/popup/two-factor.component.html b/apps/browser/src/auth/popup/two-factor.component.html index 3047ddfd7e..126b0ea5a9 100644 --- a/apps/browser/src/auth/popup/two-factor.component.html +++ b/apps/browser/src/auth/popup/two-factor.component.html @@ -111,7 +111,7 @@ -
+

{{ "duoRequiredForAccount" | i18n }}

@@ -127,17 +127,6 @@
- -
- -
- - -
-
@@ -158,7 +147,7 @@
- +
@@ -148,10 +140,7 @@
-
+
diff --git a/apps/web/src/connectors/duo.html b/apps/web/src/connectors/duo.html deleted file mode 100644 index 8d315219c2..0000000000 --- a/apps/web/src/connectors/duo.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - Bitwarden Duo Connector - - - - diff --git a/apps/web/src/connectors/duo.scss b/apps/web/src/connectors/duo.scss deleted file mode 100644 index 0fa97956ee..0000000000 --- a/apps/web/src/connectors/duo.scss +++ /dev/null @@ -1,18 +0,0 @@ -html, -body { - margin: 0; - padding: 0; -} - -body { - background: #efeff4 url("../images/loading.svg") 0 0 no-repeat; -} - -iframe { - display: block; - width: 100%; - height: 400px; - border: none; - margin: 0; - padding: 0; -} diff --git a/apps/web/src/connectors/duo.ts b/apps/web/src/connectors/duo.ts deleted file mode 100644 index b041c0d6a2..0000000000 --- a/apps/web/src/connectors/duo.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as DuoWebSDK from "duo_web_sdk"; - -import { getQsParam } from "./common"; - -require("./duo.scss"); - -document.addEventListener("DOMContentLoaded", () => { - const frameElement = document.createElement("iframe"); - frameElement.setAttribute("id", "duo_iframe"); - setFrameHeight(); - document.body.appendChild(frameElement); - - const hostParam = getQsParam("host"); - const requestParam = getQsParam("request"); - - const hostUrl = new URL("https://" + hostParam); - if ( - !hostUrl.hostname.endsWith(".duosecurity.com") && - !hostUrl.hostname.endsWith(".duofederal.com") - ) { - return; - } - - DuoWebSDK.init({ - iframe: "duo_iframe", - host: hostUrl.hostname, - sig_request: requestParam, - submit_callback: (form: any) => { - invokeCSCode(form.elements.sig_response.value); - }, - }); - - window.onresize = setFrameHeight; - - function setFrameHeight() { - frameElement.style.height = window.innerHeight + "px"; - } -}); - -function invokeCSCode(data: string) { - try { - (window as any).invokeCSharpAction(data); - } catch (err) { - // eslint-disable-next-line - console.log(err); - } -} diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index f22d98f081..884a0bc997 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -91,11 +91,6 @@ const plugins = [ chunks: ["theme_head", "app/polyfills", "app/vendor", "app/main"], }), new HtmlWebpackInjector(), - new HtmlWebpackPlugin({ - template: "./src/connectors/duo.html", - filename: "duo-connector.html", - chunks: ["connectors/duo"], - }), new HtmlWebpackPlugin({ template: "./src/connectors/webauthn.html", filename: "webauthn-connector.html", @@ -324,7 +319,6 @@ const webpackConfig = { "app/main": "./src/main.ts", "connectors/webauthn": "./src/connectors/webauthn.ts", "connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts", - "connectors/duo": "./src/connectors/duo.ts", "connectors/sso": "./src/connectors/sso.ts", "connectors/captcha": "./src/connectors/captcha.ts", "connectors/duo-redirect": "./src/connectors/duo-redirect.ts", diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 687fd3fb6f..4ef18985d8 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -1,6 +1,5 @@ import { Directive, Inject, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, NavigationExtras, Router } from "@angular/router"; -import * as DuoWebSDK from "duo_web_sdk"; import { firstValueFrom } from "rxjs"; import { first } from "rxjs/operators"; @@ -53,7 +52,6 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI emailPromise: Promise; orgIdentifier: string = null; - duoFrameless = false; duoFramelessUrl: string = null; duoResultListenerInitialized = false; @@ -177,42 +175,14 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI break; case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: - // 2 Duo 2FA flows available - // 1. Duo Web SDK (iframe) - existing, to be deprecated - // 2. Duo Frameless (new tab) - new - - // AuthUrl only exists for new Duo Frameless flow - if (providerData.AuthUrl) { - this.duoFrameless = true; - // Setup listener for duo-redirect.ts connector to send back the code - - if (!this.duoResultListenerInitialized) { - // setup client specific duo result listener - this.setupDuoResultListener(); - this.duoResultListenerInitialized = true; - } - - // flow must be launched by user so they can choose to remember the device or not. - this.duoFramelessUrl = providerData.AuthUrl; - } else { - // Duo Web SDK (iframe) flow - // TODO: remove when we remove the "duo-redirect" feature flag - setTimeout(() => { - DuoWebSDK.init({ - iframe: undefined, - host: providerData.Host, - sig_request: providerData.Signature, - submit_callback: async (f: HTMLFormElement) => { - const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement; - if (sig != null) { - this.token = sig.value; - await this.submit(); - } - }, - }); - }, 0); + // Setup listener for duo-redirect.ts connector to send back the code + if (!this.duoResultListenerInitialized) { + // setup client specific duo result listener + this.setupDuoResultListener(); + this.duoResultListenerInitialized = true; } - + // flow must be launched by user so they can choose to remember the device or not. + this.duoFramelessUrl = providerData.AuthUrl; break; case TwoFactorProviderType.Email: this.twoFactorEmail = providerData.Email; diff --git a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts index d113715b37..c67b111741 100644 --- a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts @@ -1,7 +1,7 @@ import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorDuoRequest extends SecretVerificationRequest { - integrationKey: string; - secretKey: string; + clientId: string; + clientSecret: string; host: string; } diff --git a/libs/common/src/auth/models/response/two-factor-duo.response.ts b/libs/common/src/auth/models/response/two-factor-duo.response.ts index b91c9f0fb6..a195aa236d 100644 --- a/libs/common/src/auth/models/response/two-factor-duo.response.ts +++ b/libs/common/src/auth/models/response/two-factor-duo.response.ts @@ -3,14 +3,14 @@ import { BaseResponse } from "../../../models/response/base.response"; export class TwoFactorDuoResponse extends BaseResponse { enabled: boolean; host: string; - secretKey: string; - integrationKey: string; + clientSecret: string; + clientId: string; constructor(response: any) { super(response); this.enabled = this.getResponseProperty("Enabled"); this.host = this.getResponseProperty("Host"); - this.secretKey = this.getResponseProperty("SecretKey"); - this.integrationKey = this.getResponseProperty("IntegrationKey"); + this.clientSecret = this.getResponseProperty("ClientSecret"); + this.clientId = this.getResponseProperty("ClientId"); } } diff --git a/package-lock.json b/package-lock.json index 78ed8111e0..991849fd01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,6 @@ "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.36.1", - "duo_web_sdk": "github:duosecurity/duo_web_sdk", "form-data": "4.0.0", "https-proxy-agent": "7.0.2", "inquirer": "8.2.6", @@ -97,7 +96,6 @@ "@storybook/testing-library": "0.2.2", "@types/argon2-browser": "1.18.1", "@types/chrome": "0.0.262", - "@types/duo_web_sdk": "2.7.1", "@types/firefox-webext-browser": "111.0.5", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", @@ -11352,12 +11350,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/duo_web_sdk": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/duo_web_sdk/-/duo_web_sdk-2.7.1.tgz", - "integrity": "sha512-DePanZjFww36yGSxXwC8B3AsjrrDuPxEcufeh4gTqVsUMpCYByxjX4PERiYZdW0typzKSt9E4I14PPp+PrSIQA==", - "dev": true - }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -18249,11 +18241,6 @@ "node": ">=12" } }, - "node_modules/duo_web_sdk": { - "version": "2.7.0", - "resolved": "git+ssh://git@github.com/duosecurity/duo_web_sdk.git#29cad7338eff2cd909a361ecdd525458862938be", - "license": "SEE LICENSE IN LICENSE" - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", diff --git a/package.json b/package.json index 70c3803aba..c835300fcc 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@storybook/testing-library": "0.2.2", "@types/argon2-browser": "1.18.1", "@types/chrome": "0.0.262", - "@types/duo_web_sdk": "2.7.1", "@types/firefox-webext-browser": "111.0.5", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", @@ -176,7 +175,6 @@ "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.36.1", - "duo_web_sdk": "github:duosecurity/duo_web_sdk", "form-data": "4.0.0", "https-proxy-agent": "7.0.2", "inquirer": "8.2.6",