From ff18a5b9057bf018bb52365866b32e9ff525665a Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:42:29 -0400 Subject: [PATCH 01/20] DEVOPS-1427 - Update Deploy EU Prod Web Workflow (#5637) --- .github/workflows/deploy-eu-prod-web.yml | 47 ++++++++++++++++++++++++ .github/workflows/deploy-prod-web.yml | 13 ------- 2 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/deploy-eu-prod-web.yml delete mode 100644 .github/workflows/deploy-prod-web.yml diff --git a/.github/workflows/deploy-eu-prod-web.yml b/.github/workflows/deploy-eu-prod-web.yml new file mode 100644 index 0000000000..282cea9663 --- /dev/null +++ b/.github/workflows/deploy-eu-prod-web.yml @@ -0,0 +1,47 @@ +--- +name: Deploy Web - EU Prod + +on: + workflow_dispatch: + +jobs: + azure-deploy: + name: Deploy to Azure + runs-on: ubuntu-22.04 + env: + _WEB_ARTIFACT: "web-*-cloud-euprd.zip" + steps: + - name: Login to Azure - EU Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6 + with: + creds: ${{ secrets.AZURE_KV_EU_PRD_SERVICE_PRINCIPAL }} + + - name: Retrieve Storage Account connection string + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b + with: + keyvault: webvault-westeurope-prod + secrets: "sa-bitwarden-web-vault-dev-key-temp" + + - name: Download latest cloud asset + uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a + with: + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: ${{ env._WEB_ARTIFACT }} + + - name: Unzip build asset + working-directory: apps/web + run: unzip ${{ env._WEB_ARTIFACT }} + + - name: Deploy to Azure Storage Account + working-directory: apps/web + run: | + az storage blob upload-batch --source "./build" \ + --destination '$web' \ + --account-name "bwwebvault1itgprod" \ + --connection-string "${{ steps.retrieve-secrets.outputs.sa-bitwarden-web-vault-dev-key-temp }}" \ + --overwrite \ + --no-progress diff --git a/.github/workflows/deploy-prod-web.yml b/.github/workflows/deploy-prod-web.yml deleted file mode 100644 index 144b23e390..0000000000 --- a/.github/workflows/deploy-prod-web.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Deploy Web - EU Prod - STUB - -on: - workflow_dispatch: - -jobs: - stub-job: - name: Stub Job - runs-on: ubuntu-22.04 - steps: - - name: Stub Step - run: exit 0 From 4124f7bdc8753ffc666f18b0099760f3e95d9976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bispo?= Date: Tue, 20 Jun 2023 20:44:46 +0100 Subject: [PATCH 02/20] [PM-2676] Fix web set environment urls after state init (#5632) * [PM-2676] Fix web set env urls after state init. * [PM-2676] Add note to remove workaround --- apps/web/src/app/core/init.service.ts | 4 ++++ .../environment-selector.component.ts | 3 +-- .../abstractions/environment.service.ts | 1 + .../platform/services/environment.service.ts | 17 +++++++++++------ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 60eeeeea18..4a2ef2f495 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -38,11 +38,15 @@ export class InitService { init() { return async () => { + // Workaround to ignore stateService.activeAccount until process.env.URLS are set + // TODO: Remove this when implementing ticket PM-2637 + this.environmentService.initialized = false; await this.stateService.init(); const urls = process.env.URLS as Urls; urls.base ??= this.win.location.origin; this.environmentService.setUrls(urls); + this.environmentService.initialized = true; setTimeout(() => this.notificationsService.init(), 3000); (this.vaultTimeoutService as VaultTimeoutService).init(true); diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index 7989e0f2ca..d622b4c6db 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -88,11 +88,10 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy { } async updateEnvironmentInfo() { + this.selectedEnvironment = this.environmentService.selectedRegion; this.euServerFlagEnabled = await this.configService.getFeatureFlagBool( FeatureFlag.DisplayEuEnvironmentFlag ); - - this.selectedEnvironment = this.environmentService.selectedRegion; } close() { diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 68327f4fd6..aa963baf0a 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -28,6 +28,7 @@ export abstract class EnvironmentService { usUrls: Urls; euUrls: Urls; selectedRegion?: Region; + initialized = true; hasBaseUrl: () => boolean; getNotificationsUrl: () => string; diff --git a/libs/common/src/platform/services/environment.service.ts b/libs/common/src/platform/services/environment.service.ts index b2f62a22b1..6b27f9c13c 100644 --- a/libs/common/src/platform/services/environment.service.ts +++ b/libs/common/src/platform/services/environment.service.ts @@ -12,6 +12,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { private readonly urlsSubject = new Subject(); urls: Observable = this.urlsSubject.asObservable(); selectedRegion?: Region; + initialized = true; protected baseUrl: string; protected webVaultUrl: string; @@ -49,6 +50,9 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { this.stateService.activeAccount$ .pipe( concatMap(async () => { + if (!this.initialized) { + return; + } await this.setUrlsFromStorage(); }) ) @@ -157,22 +161,23 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { // fix environment urls for old users if (savedUrls.base === "https://vault.bitwarden.com") { - this.setRegion(Region.US); + await this.setRegion(Region.US); return; } if (savedUrls.base === "https://vault.bitwarden.eu") { - this.setRegion(Region.EU); + await this.setRegion(Region.EU); return; } switch (region) { case Region.EU: - this.setRegion(Region.EU); + await this.setRegion(Region.EU); return; case Region.US: - this.setRegion(Region.US); + await this.setRegion(Region.US); return; case Region.SelfHosted: + case null: default: this.baseUrl = envUrls.base = savedUrls.base; this.webVaultUrl = savedUrls.webVault; @@ -182,9 +187,9 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { this.notificationsUrl = savedUrls.notifications; this.eventsUrl = envUrls.events = savedUrls.events; this.keyConnectorUrl = savedUrls.keyConnector; + await this.setRegion(Region.SelfHosted); // scimUrl is not saved to storage this.urlsSubject.next(); - this.setRegion(Region.SelfHosted); break; } } @@ -270,7 +275,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { case Region.SelfHosted: // if user saves with empty fields, default to US if (this.isEmpty()) { - this.setRegion(Region.US); + await this.setRegion(Region.US); } break; } From 4dc34fc7a87c5e4eed341f6ac86bccdf7d4f559c Mon Sep 17 00:00:00 2001 From: Andrew Dassonville Date: Wed, 21 Jun 2023 08:18:46 -0700 Subject: [PATCH 03/20] [PM-2609] Allow auto-filling TOTP codes (#2142) * Begin implementing TOTP autofill * Add support for Cloudflare * Fix linting errors * Add GitHub support * Automatically check for autocomplete="one-time-code" * Fix TOTP-filling for Steam * Make auto-fill on page load work for TOTP * [PM-2609] Introduce logic to handle skipping autofill of TOTP on page load * [PM-2609] Ensuring other forms of user initiated autofill can autofill the TOTP value for a vault item --------- Co-authored-by: Daniel James Smith Co-authored-by: Cesar Gonzalez Co-authored-by: Cesar Gonzalez --- .../autofill/commands/autofill-tab-command.ts | 1 + .../services/abstractions/autofill.service.ts | 1 + .../autofill/services/autofill-constants.ts | 11 + .../src/autofill/services/autofill.service.ts | 247 +++++++++++++----- .../src/background/runtime.background.ts | 1 + .../components/vault/current-tab.component.ts | 1 + .../popup/components/vault/view.component.ts | 1 + 7 files changed, 195 insertions(+), 68 deletions(-) diff --git a/apps/browser/src/autofill/commands/autofill-tab-command.ts b/apps/browser/src/autofill/commands/autofill-tab-command.ts index 4910a6cf6f..b51edd929e 100644 --- a/apps/browser/src/autofill/commands/autofill-tab-command.ts +++ b/apps/browser/src/autofill/commands/autofill-tab-command.ts @@ -46,6 +46,7 @@ export class AutofillTabCommand { onlyEmptyFields: false, onlyVisibleFields: false, fillNewPassword: true, + allowTotpAutofill: true, }); } diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index b3ad26f0a0..18830b3210 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -21,6 +21,7 @@ export interface AutoFillOptions { fillNewPassword?: boolean; skipLastUsed?: boolean; allowUntrustedIframe?: boolean; + allowTotpAutofill?: boolean; } export interface FormData { diff --git a/apps/browser/src/autofill/services/autofill-constants.ts b/apps/browser/src/autofill/services/autofill-constants.ts index fcab50712c..58c295c0c2 100644 --- a/apps/browser/src/autofill/services/autofill-constants.ts +++ b/apps/browser/src/autofill/services/autofill-constants.ts @@ -20,6 +20,17 @@ export class AutoFillConstants { "benutzer id", ]; + static readonly TotpFieldNames: string[] = [ + "totp", + "2fa", + "mfa", + "totpcode", + "2facode", + "mfacode", + "twofactor", + "twofactorcode", + ]; + static readonly PasswordFieldIgnoreList: string[] = [ "onetimepassword", "captcha", diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 54e7a833b6..493e463c52 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -32,6 +32,7 @@ export interface GenerateFillScriptOptions { onlyEmptyFields: boolean; onlyVisibleFields: boolean; fillNewPassword: boolean; + allowTotpAutofill: boolean; cipher: CipherView; tabUrl: string; defaultUriMatch: UriMatchType; @@ -127,69 +128,72 @@ export default class AutofillService implements AutofillServiceInterface { const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain; let didAutofill = false; - options.pageDetails.forEach((pd) => { - // make sure we're still on correct tab - if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) { - return; - } - - const fillScript = this.generateFillScript(pd.details, { - skipUsernameOnlyFill: options.skipUsernameOnlyFill || false, - onlyEmptyFields: options.onlyEmptyFields || false, - onlyVisibleFields: options.onlyVisibleFields || false, - fillNewPassword: options.fillNewPassword || false, - cipher: options.cipher, - tabUrl: tab.url, - defaultUriMatch: defaultUriMatch, - }); - - if (!fillScript || !fillScript.script || !fillScript.script.length) { - return; - } - - if ( - fillScript.untrustedIframe && - options.allowUntrustedIframe != undefined && - !options.allowUntrustedIframe - ) { - this.logService.info("Auto-fill on page load was blocked due to an untrusted iframe."); - return; - } - - // Add a small delay between operations - fillScript.properties.delay_between_operations = 20; - - didAutofill = true; - if (!options.skipLastUsed) { - this.cipherService.updateLastUsedDate(options.cipher.id); - } - - BrowserApi.tabSendMessage( - tab, - { - command: "fillForm", - fillScript: fillScript, - url: tab.url, - }, - { frameId: pd.frameId } - ); - - if ( - options.cipher.type !== CipherType.Login || - totpPromise || - !options.cipher.login.totp || - (!canAccessPremium && !options.cipher.organizationUseTotp) - ) { - return; - } - - totpPromise = this.stateService.getDisableAutoTotpCopy().then((disabled) => { - if (!disabled) { - return this.totpService.getCode(options.cipher.login.totp); + await Promise.all( + options.pageDetails.map(async (pd) => { + // make sure we're still on correct tab + if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) { + return; } - return null; - }); - }); + + const fillScript = await this.generateFillScript(pd.details, { + skipUsernameOnlyFill: options.skipUsernameOnlyFill || false, + onlyEmptyFields: options.onlyEmptyFields || false, + onlyVisibleFields: options.onlyVisibleFields || false, + fillNewPassword: options.fillNewPassword || false, + allowTotpAutofill: options.allowTotpAutofill || false, + cipher: options.cipher, + tabUrl: tab.url, + defaultUriMatch: defaultUriMatch, + }); + + if (!fillScript || !fillScript.script || !fillScript.script.length) { + return; + } + + if ( + fillScript.untrustedIframe && + options.allowUntrustedIframe != undefined && + !options.allowUntrustedIframe + ) { + this.logService.info("Auto-fill on page load was blocked due to an untrusted iframe."); + return; + } + + // Add a small delay between operations + fillScript.properties.delay_between_operations = 20; + + didAutofill = true; + if (!options.skipLastUsed) { + this.cipherService.updateLastUsedDate(options.cipher.id); + } + + BrowserApi.tabSendMessage( + tab, + { + command: "fillForm", + fillScript: fillScript, + url: tab.url, + }, + { frameId: pd.frameId } + ); + + if ( + options.cipher.type !== CipherType.Login || + totpPromise || + !options.cipher.login.totp || + (!canAccessPremium && !options.cipher.organizationUseTotp) + ) { + return; + } + + totpPromise = this.stateService.getDisableAutoTotpCopy().then((disabled) => { + if (!disabled) { + return this.totpService.getCode(options.cipher.login.totp); + } + return null; + }); + }) + ); if (didAutofill) { this.eventCollectionService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id); @@ -244,6 +248,7 @@ export default class AutofillService implements AutofillServiceInterface { onlyVisibleFields: !fromCommand, fillNewPassword: fromCommand, allowUntrustedIframe: fromCommand, + allowTotpAutofill: fromCommand, }); // Update last used index as autofill has succeed @@ -280,10 +285,10 @@ export default class AutofillService implements AutofillServiceInterface { return tab; } - private generateFillScript( + private async generateFillScript( pageDetails: AutofillPageDetails, options: GenerateFillScriptOptions - ): AutofillScript { + ): Promise { if (!pageDetails || !options.cipher) { return null; } @@ -333,7 +338,12 @@ export default class AutofillService implements AutofillServiceInterface { switch (options.cipher.type) { case CipherType.Login: - fillScript = this.generateLoginFillScript(fillScript, pageDetails, filledFields, options); + fillScript = await this.generateLoginFillScript( + fillScript, + pageDetails, + filledFields, + options + ); break; case CipherType.Card: fillScript = this.generateCardFillScript(fillScript, pageDetails, filledFields, options); @@ -353,20 +363,22 @@ export default class AutofillService implements AutofillServiceInterface { return fillScript; } - private generateLoginFillScript( + private async generateLoginFillScript( fillScript: AutofillScript, pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, options: GenerateFillScriptOptions - ): AutofillScript { + ): Promise { if (!options.cipher.login) { return null; } const passwords: AutofillField[] = []; const usernames: AutofillField[] = []; + const totps: AutofillField[] = []; let pf: AutofillField = null; let username: AutofillField = null; + let totp: AutofillField = null; const login = options.cipher.login; fillScript.savedUrls = login?.uris?.filter((u) => u.match != UriMatchType.Never).map((u) => u.uri) ?? []; @@ -420,6 +432,19 @@ export default class AutofillService implements AutofillServiceInterface { usernames.push(username); } } + + if (options.allowTotpAutofill && login.totp) { + totp = this.findTotpField(pageDetails, pf, false, false, false); + + if (!totp && !options.onlyVisibleFields) { + // not able to find any viewable totp fields. maybe there are some "hidden" ones? + totp = this.findTotpField(pageDetails, pf, true, true, true); + } + + if (totp) { + totps.push(totp); + } + } }); } @@ -442,18 +467,42 @@ export default class AutofillService implements AutofillServiceInterface { usernames.push(username); } } + + if (options.allowTotpAutofill && login.totp && pf.elementNumber > 0) { + totp = this.findTotpField(pageDetails, pf, false, false, true); + + if (!totp && !options.onlyVisibleFields) { + // not able to find any viewable username fields. maybe there are some "hidden" ones? + totp = this.findTotpField(pageDetails, pf, true, true, true); + } + + if (totp) { + totps.push(totp); + } + } } - if (!passwordFields.length && !options.skipUsernameOnlyFill) { + if (!passwordFields.length) { // No password fields on this page. Let's try to just fuzzy fill the username. pageDetails.fields.forEach((f) => { if ( + !options.skipUsernameOnlyFill && f.viewable && (f.type === "text" || f.type === "email" || f.type === "tel") && AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames) ) { usernames.push(f); } + + if ( + options.allowTotpAutofill && + f.viewable && + (f.type === "text" || f.type === "number") && + (AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames) || + f.autoCompleteType === "one-time-code") + ) { + totps.push(f); + } }); } @@ -477,6 +526,20 @@ export default class AutofillService implements AutofillServiceInterface { AutofillService.fillByOpid(fillScript, p, login.password); }); + if (options.allowTotpAutofill) { + await Promise.all( + totps.map(async (t) => { + if (Object.prototype.hasOwnProperty.call(filledFields, t.opid)) { + return; + } + + filledFields[t.opid] = t; + const totpValue = await this.totpService.getCode(login.totp); + AutofillService.fillByOpid(fillScript, t, totpValue); + }) + ); + } + fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript); return fillScript; } @@ -1258,6 +1321,42 @@ export default class AutofillService implements AutofillServiceInterface { return usernameField; } + private findTotpField( + pageDetails: AutofillPageDetails, + passwordField: AutofillField, + canBeHidden: boolean, + canBeReadOnly: boolean, + withoutForm: boolean + ) { + let totpField: AutofillField = null; + for (let i = 0; i < pageDetails.fields.length; i++) { + const f = pageDetails.fields[i]; + if (AutofillService.forCustomFieldsOnly(f)) { + continue; + } + + if ( + !f.disabled && + (canBeReadOnly || !f.readonly) && + (withoutForm || f.form === passwordField.form) && + (canBeHidden || f.viewable) && + (f.type === "text" || f.type === "number") + ) { + totpField = f; + + if ( + this.findMatchingFieldIndex(f, AutoFillConstants.TotpFieldNames) > -1 || + f.autoCompleteType === "one-time-code" + ) { + // We found an exact match. No need to keep looking. + break; + } + } + } + + return totpField; + } + private findMatchingFieldIndex(field: AutofillField, names: string[]): number { for (let i = 0; i < names.length; i++) { if (names[i].indexOf("=") > -1) { @@ -1267,6 +1366,12 @@ export default class AutofillService implements AutofillServiceInterface { if (this.fieldPropertyIsPrefixMatch(field, "htmlName", names[i], "name")) { return i; } + if (this.fieldPropertyIsPrefixMatch(field, "label-left", names[i], "label")) { + return i; + } + if (this.fieldPropertyIsPrefixMatch(field, "label-right", names[i], "label")) { + return i; + } if (this.fieldPropertyIsPrefixMatch(field, "label-tag", names[i], "label")) { return i; } @@ -1284,6 +1389,12 @@ export default class AutofillService implements AutofillServiceInterface { if (this.fieldPropertyIsMatch(field, "htmlName", names[i])) { return i; } + if (this.fieldPropertyIsMatch(field, "label-left", names[i])) { + return i; + } + if (this.fieldPropertyIsMatch(field, "label-right", names[i])) { + return i; + } if (this.fieldPropertyIsMatch(field, "label-tag", names[i])) { return i; } diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index a06ba43eff..81f7376a94 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -224,6 +224,7 @@ export default class RuntimeBackground { cipher: this.main.loginToAutoFill, pageDetails: this.pageDetailsToAutoFill, fillNewPassword: true, + allowTotpAutofill: true, }); if (totpCode != null) { diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts index a48c700375..4b2c62a0e0 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -180,6 +180,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy { pageDetails: this.pageDetails, doc: window.document, fillNewPassword: true, + allowTotpAutofill: true, }); if (this.totpCode != null) { this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index efdb48b95c..aa17c16f26 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -288,6 +288,7 @@ export class ViewComponent extends BaseViewComponent { pageDetails: this.pageDetails, doc: window.document, fillNewPassword: true, + allowTotpAutofill: true, }); if (this.totpCode != null) { this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); From 37964f7cbf6b8c4ac8605870ef135318564592eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:37:55 -0400 Subject: [PATCH 04/20] Bumped web version to 2023.5.1 (#5646) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 1aed639fdc..5b5f25d437 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2023.5.0", + "version": "2023.5.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 5a380576c9..201de35a0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -243,7 +243,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2023.5.0" + "version": "2023.5.1" }, "libs/angular": { "name": "@bitwarden/angular", From 19d2b2594c04fa1eeef99c57de962bda1f01b1cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:39:08 -0400 Subject: [PATCH 05/20] Bumped browser version to 2023.5.1 (#5647) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- package-lock.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 4872af87d3..7eb29bcc05 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2023.5.0", + "version": "2023.5.1", "scripts": { "build": "webpack", "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 429e18cf52..e969f2924a 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2023.5.0", + "version": "2023.5.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 5f76416c3b..face29b7da 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2023.5.0", + "version": "2023.5.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/package-lock.json b/package-lock.json index 201de35a0a..39e2f92354 100644 --- a/package-lock.json +++ b/package-lock.json @@ -189,7 +189,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2023.5.0" + "version": "2023.5.1" }, "apps/cli": { "name": "@bitwarden/cli", From a2b290a31e60a9dba714b938bbff9bbd0537be3c Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 22 Jun 2023 08:29:37 -0400 Subject: [PATCH 06/20] Remove password options from `serve` unlock (#5601) These options are no longer considered safe as the file location or environment variable could be guessed by an attacker. --- apps/cli/src/commands/serve.command.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 92e8747bc3..8808dcaafb 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -245,6 +245,10 @@ export class ServeCommand { }); router.post("/unlock", async (ctx, next) => { + // Do not allow guessing password location through serve command + delete ctx.request.query.passwordFile; + delete ctx.request.query.passwordEnv; + const response = await this.unlockCommand.run( ctx.request.body.password == null ? null : (ctx.request.body.password as string), ctx.request.query From b9d6d6583e8d7cfa160668a0abc0bd14345d4fd7 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:57:15 -0400 Subject: [PATCH 07/20] Fix Chocolately command (#5653) --- .github/workflows/release-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index e883252814..796b72909a 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -368,5 +368,5 @@ jobs: - name: Push to Chocolatey if: ${{ github.event.inputs.release_type != 'Dry Run' }} shell: pwsh - run: choco push + run: choco push --source=https://push.chocolatey.org/ working-directory: apps/desktop/dist From 4820c883353f168af421f87e97fadd5307941375 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 22 Jun 2023 12:59:24 -0400 Subject: [PATCH 08/20] Removed feature flag logic for low kdf iteration (#5640) --- .../app/vault/individual-vault/vault.component.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 176fb3fcb3..6e07915662 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -38,7 +38,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { DEFAULT_PBKDF2_ITERATIONS, EventType, KdfType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils"; import { TreeNode } from "@bitwarden/common/models/domain/tree-node"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -857,17 +856,9 @@ export class VaultComponent implements OnInit, OnDestroy { } async isLowKdfIteration() { - const showLowKdfEnabled = await this.configService.getFeatureFlagBool( - FeatureFlag.DisplayLowKdfIterationWarningFlag - ); - - if (showLowKdfEnabled) { - const kdfType = await this.stateService.getKdfType(); - const kdfOptions = await this.stateService.getKdfConfig(); - return kdfType === KdfType.PBKDF2_SHA256 && kdfOptions.iterations < DEFAULT_PBKDF2_ITERATIONS; - } - - return showLowKdfEnabled; + const kdfType = await this.stateService.getKdfType(); + const kdfOptions = await this.stateService.getKdfConfig(); + return kdfType === KdfType.PBKDF2_SHA256 && kdfOptions.iterations < DEFAULT_PBKDF2_ITERATIONS; } protected async repromptCipher(ciphers: CipherView[]) { From 5a674d469d3f93166bda364d5fe18f60a292c2f3 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:36:21 -0400 Subject: [PATCH 09/20] Add tag input to Deploy Web EU-PRD workflow (#5658) --- .github/workflows/deploy-eu-prod-web.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-eu-prod-web.yml b/.github/workflows/deploy-eu-prod-web.yml index 282cea9663..dd44583ab2 100644 --- a/.github/workflows/deploy-eu-prod-web.yml +++ b/.github/workflows/deploy-eu-prod-web.yml @@ -1,8 +1,14 @@ --- -name: Deploy Web - EU Prod +name: Deploy Web to EU-PRD Cloud on: workflow_dispatch: + inputs: + tag: + description: "Branch name to deploy (examples: 'master', 'feature/sm')" + required: true + type: string + default: master jobs: azure-deploy: @@ -29,7 +35,7 @@ jobs: workflow: build-web.yml path: apps/web workflow_conclusion: success - branch: ${{ github.ref_name }} + branch: ${{ github.event.inputs.tag }} artifacts: ${{ env._WEB_ARTIFACT }} - name: Unzip build asset From d10741e994ae1f1885199c451ba166b8a535cbea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 20:37:42 +0000 Subject: [PATCH 10/20] Update bitwarden/gh-actions digest to 37ffa14 (#5589) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/brew-bump-cli.yml | 2 +- .github/workflows/brew-bump-desktop.yml | 2 +- .github/workflows/build-browser.yml | 4 ++-- .github/workflows/build-cli.yml | 2 +- .github/workflows/build-desktop.yml | 8 +++---- .github/workflows/build-web.yml | 8 +++---- .github/workflows/crowdin-pull.yml | 4 ++-- .github/workflows/deploy-eu-prod-web.yml | 4 ++-- .github/workflows/deploy-non-prod-web.yml | 2 +- .github/workflows/release-browser.yml | 6 ++--- .github/workflows/release-cli.yml | 24 ++++++++++---------- .github/workflows/release-desktop-beta.yml | 8 +++---- .github/workflows/release-desktop.yml | 22 +++++++++--------- .github/workflows/release-qa-web.yml | 2 +- .github/workflows/release-web.yml | 14 ++++++------ .github/workflows/staged-rollout-desktop.yml | 2 +- .github/workflows/version-bump.yml | 6 ++--- .github/workflows/workflow-linter.yml | 2 +- 18 files changed, 61 insertions(+), 61 deletions(-) diff --git a/.github/workflows/brew-bump-cli.yml b/.github/workflows/brew-bump-cli.yml index cae9db880e..d45e85ea15 100644 --- a/.github/workflows/brew-bump-cli.yml +++ b/.github/workflows/brew-bump-cli.yml @@ -23,7 +23,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "brew-bump-workflow-pat" diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml index 43b24f4553..b7bb726722 100644 --- a/.github/workflows/brew-bump-desktop.yml +++ b/.github/workflows/brew-bump-desktop.yml @@ -23,7 +23,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "brew-bump-workflow-pat" diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 89fa40a53f..4e9a15fbfa 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -354,7 +354,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -416,7 +416,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index bc33a4ccd0..c047133e1c 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -404,7 +404,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index b5da80f7f7..d00551ca36 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -277,7 +277,7 @@ jobs: node-gyp install $(node -v) - name: Install AST - uses: bitwarden/gh-actions/install-ast@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/install-ast@37ffa14164a7308bc273829edfe75c97cd562375 - name: Set up environmentF run: choco install checksum --no-progress @@ -302,7 +302,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "code-signing-vault-url, @@ -1190,7 +1190,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -1269,7 +1269,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index fd4a700131..4a7918a81b 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -235,7 +235,7 @@ jobs: - name: Retrieve github PAT secrets id: retrieve-secret-pat - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -243,7 +243,7 @@ jobs: - name: Setup DCT if: ${{ env.is_publish_branch == 'true' }} id: setup-dct - uses: bitwarden/gh-actions/setup-docker-trust@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/setup-docker-trust@37ffa14164a7308bc273829edfe75c97cd562375 with: azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} azure-keyvault-name: "bitwarden-ci" @@ -291,7 +291,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token" @@ -352,7 +352,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets if: failure() - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "devops-alerts-slack-webhook-url" diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index f058c71203..44c753582c 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -32,13 +32,13 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" - name: Download translations - uses: bitwarden/gh-actions/crowdin@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/crowdin@37ffa14164a7308bc273829edfe75c97cd562375 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/deploy-eu-prod-web.yml b/.github/workflows/deploy-eu-prod-web.yml index dd44583ab2..55cc10825b 100644 --- a/.github/workflows/deploy-eu-prod-web.yml +++ b/.github/workflows/deploy-eu-prod-web.yml @@ -24,13 +24,13 @@ jobs: - name: Retrieve Storage Account connection string id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: webvault-westeurope-prod secrets: "sa-bitwarden-web-vault-dev-key-temp" - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/deploy-non-prod-web.yml b/.github/workflows/deploy-non-prod-web.yml index e0b6977a78..d041369d84 100644 --- a/.github/workflows/deploy-non-prod-web.yml +++ b/.github/workflows/deploy-non-prod-web.yml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index c9f9b20c62..1aebc9722c 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -41,7 +41,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375 with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -103,7 +103,7 @@ jobs: - name: Download latest Release build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-browser.yml workflow_conclusion: success @@ -116,7 +116,7 @@ jobs: - name: Dry Run - Download latest master build artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-browser.yml workflow_conclusion: success diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 406b345c2f..76b4915dbd 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -57,7 +57,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375 with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -78,7 +78,7 @@ jobs: - name: Download all Release artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli @@ -87,7 +87,7 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli @@ -150,7 +150,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "snapcraft-store-token" @@ -162,7 +162,7 @@ jobs: - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli @@ -172,7 +172,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli @@ -204,7 +204,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "cli-choco-api-key" @@ -220,7 +220,7 @@ jobs: - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli/dist @@ -230,7 +230,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli/dist @@ -263,14 +263,14 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "npm-api-key" - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli/build @@ -280,7 +280,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-cli.yml path: apps/cli/build diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index f9a5e4d5ad..87f188e9b1 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375 with: release-type: 'Initial Release' project-type: ts @@ -231,7 +231,7 @@ jobs: node-gyp install $(node -v) - name: Install AST - uses: bitwarden/gh-actions/install-ast@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/install-ast@37ffa14164a7308bc273829edfe75c97cd562375 - name: Set up environment run: choco install checksum --no-progress @@ -249,7 +249,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "code-signing-vault-url, @@ -932,7 +932,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 796b72909a..4ad9083683 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -67,7 +67,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375 with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -110,7 +110,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, @@ -123,7 +123,7 @@ jobs: - name: Download all artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success @@ -132,7 +132,7 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success @@ -185,7 +185,7 @@ jobs: --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-checksum@37ffa14164a7308bc273829edfe75c97cd562375 with: packages_dir: "apps/desktop/artifacts" file_path: "apps/desktop/artifacts/sha256-checksums.txt" @@ -263,7 +263,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "snapcraft-store-token" @@ -279,7 +279,7 @@ jobs: - name: Download Snap artifact if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success @@ -289,7 +289,7 @@ jobs: - name: Dry Run - Download Snap artifact if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success @@ -329,7 +329,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "cli-choco-api-key" @@ -347,7 +347,7 @@ jobs: - name: Download choco artifact if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success @@ -357,7 +357,7 @@ jobs: - name: Dry Run - Download choco artifact if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-desktop.yml workflow_conclusion: success diff --git a/.github/workflows/release-qa-web.yml b/.github/workflows/release-qa-web.yml index 60f38e9d32..044099052b 100644 --- a/.github/workflows/release-qa-web.yml +++ b/.github/workflows/release-qa-web.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Download latest cloud asset - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: apps/web diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 1ba24a8369..391f0f8715 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -38,7 +38,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375 with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -70,7 +70,7 @@ jobs: ########## DockerHub ########## - name: Setup DCT id: setup-dct - uses: bitwarden/gh-actions/setup-docker-trust@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/setup-docker-trust@37ffa14164a7308bc273829edfe75c97cd562375 with: azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} azure-keyvault-name: "bitwarden-ci" @@ -156,7 +156,7 @@ jobs: - name: Retrieve bot secrets id: retrieve-bot-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: bitwarden-ci secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -170,7 +170,7 @@ jobs: - name: Download latest cloud asset if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: assets @@ -180,7 +180,7 @@ jobs: - name: Dry Run - Download latest cloud asset if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: assets @@ -253,7 +253,7 @@ jobs: - name: Download latest build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: apps/web/artifacts @@ -264,7 +264,7 @@ jobs: - name: Dry Run - Download latest build artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375 with: workflow: build-web.yml path: apps/web/artifacts diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index 7221028df2..a21413dcc6 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -26,7 +26,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index cebe740b9d..1c3e2c7064 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -49,7 +49,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375 with: keyvault: "bitwarden-ci" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" @@ -86,14 +86,14 @@ jobs: - name: Bump Browser Version - Manifest if: ${{ github.event.inputs.client == 'Browser' || github.event.inputs.client == 'All' }} - uses: bitwarden/gh-actions/version-bump@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/version-bump@37ffa14164a7308bc273829edfe75c97cd562375 with: version: ${{ github.event.inputs.version_number }} file_path: "apps/browser/src/manifest.json" - name: Bump Browser Version - Manifest v3 if: ${{ github.event.inputs.client == 'Browser' || github.event.inputs.client == 'All' }} - uses: bitwarden/gh-actions/version-bump@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/version-bump@37ffa14164a7308bc273829edfe75c97cd562375 with: version: ${{ github.event.inputs.version_number }} file_path: "apps/browser/src/manifest.v3.json" diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index 20525879d5..9fe167ad72 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -8,4 +8,4 @@ on: jobs: call-workflow: - uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@72594be690a4e7bfa87b1402b2aedc75acdbff12 + uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@37ffa14164a7308bc273829edfe75c97cd562375 From 797ca073b8550445c6958e40dc6241ffabf58cd9 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Thu, 22 Jun 2023 16:17:38 -0700 Subject: [PATCH 11/20] [AC-1423] Update organization subscription cloud page (#5614) * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Make "Password Manager" the default fallback for product name --- ...nization-subscription-cloud.component.html | 168 +++++++++--------- ...ganization-subscription-cloud.component.ts | 35 ++++ .../secrets-manager/enroll.component.html | 4 +- apps/web/src/app/shared/shared.module.ts | 3 + apps/web/src/locales/en/messages.json | 9 + .../models/data/organization.data.ts | 2 + .../models/domain/organization.ts | 2 + .../response/profile-organization.response.ts | 2 + .../enums/bitwarden-product-type.enum.ts | 4 + .../models/response/subscription.response.ts | 5 + 10 files changed, 147 insertions(+), 87 deletions(-) create mode 100644 libs/common/src/billing/enums/bitwarden-product-type.enum.ts diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index c28bdd4ddc..20dc22597f 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -1,5 +1,5 @@ - + + -

{{ "storage" | i18n }}

-

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

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

{{ "storage" | i18n }}

+

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

+ -
-
+
+
-
@@ -179,11 +179,11 @@ -

{{ "selfHostingTitle" | i18n }}

-

+

{{ "selfHostingTitle" | i18n }}

+

{{ "selfHostingEnterpriseOrganizationSectionCopy" | i18n }}

-
+
-
+
-

{{ "additionalOptions" | i18n }}

-

+

{{ "additionalOptions" | i18n }}

+

{{ "additionalOptionsDesc" | i18n }}

-
+
- diff --git a/apps/web/src/app/billing/organizations/secrets-manager/enroll.component.ts b/apps/web/src/app/billing/organizations/secrets-manager/enroll.component.ts deleted file mode 100644 index edcedc8d54..0000000000 --- a/apps/web/src/app/billing/organizations/secrets-manager/enroll.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, Input, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; - -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationEnrollSecretsManagerRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-enroll-secrets-manager.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; - -import { flagEnabled } from "../../../../utils/flags"; - -@Component({ - selector: "sm-enroll", - templateUrl: "enroll.component.html", -}) -export class SecretsManagerEnrollComponent implements OnInit { - @Input() enabled: boolean; - @Input() organizationId: string; - - protected formGroup = this.formBuilder.group({ - enabled: [false], - }); - - protected showSecretsManager = false; - - constructor( - private formBuilder: FormBuilder, - private organizationApiService: OrganizationApiServiceAbstraction, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private syncService: SyncService - ) { - this.showSecretsManager = flagEnabled("secretsManager"); - } - - ngOnInit(): void { - this.formGroup.setValue({ - enabled: this.enabled, - }); - } - - protected submit = async () => { - this.formGroup.markAllAsTouched(); - - const request = new OrganizationEnrollSecretsManagerRequest(); - request.enabled = this.formGroup.value.enabled; - - await this.organizationApiService.updateEnrollSecretsManager(this.organizationId, request); - await this.syncService.fullSync(true); - this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated")); - }; -} diff --git a/apps/web/src/app/billing/organizations/secrets-manager/index.ts b/apps/web/src/app/billing/organizations/secrets-manager/index.ts new file mode 100644 index 0000000000..fc0bcd3534 --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/index.ts @@ -0,0 +1,3 @@ +export * from "./sm-billing.module"; +export * from "./sm-subscribe.component"; +export * from "./sm-subscribe-standalone.component"; diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts new file mode 100644 index 0000000000..a46286fc5a --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../../../shared"; + +import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component"; +import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component"; + +@NgModule({ + imports: [SharedModule], + declarations: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent], + exports: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent], +}) +export class SecretsManagerBillingModule {} diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.html b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.html new file mode 100644 index 0000000000..84c74ee428 --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.ts new file mode 100644 index 0000000000..1aa95a6c7a --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe-standalone.component.ts @@ -0,0 +1,42 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { secretsManagerSubscribeFormFactory } from "./sm-subscribe.component"; + +@Component({ + selector: "sm-subscribe-standalone", + templateUrl: "sm-subscribe-standalone.component.html", +}) +export class SecretsManagerSubscribeStandaloneComponent { + @Input() plan: PlanResponse; + @Input() organization: Organization; + @Output() onSubscribe = new EventEmitter(); + + formGroup = secretsManagerSubscribeFormFactory(this.formBuilder); + + constructor( + private formBuilder: FormBuilder, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private organizationApiService: OrganizationApiServiceAbstraction + ) {} + + submit = async () => { + const request = new SecretsManagerSubscribeRequest(); + request.additionalSmSeats = this.formGroup.value.userSeats; + request.additionalServiceAccounts = this.formGroup.value.additionalServiceAccounts; + + await this.organizationApiService.subscribeToSecretsManager(this.organization.id, request); + + this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated")); + + this.onSubscribe.emit(); + }; +} diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.html b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.html new file mode 100644 index 0000000000..e0a1f2066e --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.html @@ -0,0 +1,68 @@ +
+

{{ "moreFromBitwarden" | i18n }}

+
+
+ +
+
+
+

{{ "secretsManagerForPlan" | i18n : planName }}

+
+ {{ "secretsManagerForPlanDesc" | i18n }} +
    +
  • {{ "limitedUsers" | i18n : maxUsers }}
  • +
  • {{ "unlimitedSecrets" | i18n }}
  • +
  • + {{ "projectsIncluded" | i18n : maxProjects }} +
  • + +
  • {{ "unlimitedProjects" | i18n }}
  • +
    +
  • {{ "serviceAccountsIncluded" | i18n : serviceAccountsIncluded }}
  • +
  • + {{ + "additionalServiceAccountCost" | i18n : (monthlyCostPerServiceAccount | currency : "$") + }} +
  • +
+
+ +
+ + {{ "costPerUser" | i18n : (monthlyCostPerUser | currency : "$") }} /{{ "month" | i18n }} + + + {{ "freeForever" | i18n }} + +
+ + + + {{ "addSecretsManager" | i18n }} + {{ "addSecretsManagerUpgradeDesc" | i18n }} + + +
+ + {{ "userSeats" | i18n }} + + {{ "userSeatsHowManyDesc" | i18n }} + + + + {{ "additionalServiceAccounts" | i18n }} + + {{ + "additionalServiceAccountsDesc" + | i18n : serviceAccountsIncluded : (monthlyCostPerServiceAccount | currency : "$") + }} + + + +
+
+
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.ts new file mode 100644 index 0000000000..ef6b73584a --- /dev/null +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-subscribe.component.ts @@ -0,0 +1,104 @@ +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { Subject, startWith, takeUntil } from "rxjs"; + +import { ControlsOf } from "@bitwarden/angular/types/controls-of"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { ProductType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { SecretsManagerLogo } from "../../../../../../../bitwarden_license/bit-web/src/app/secrets-manager/layout/secrets-manager-logo"; + +export interface SecretsManagerSubscription { + enabled: boolean; + userSeats: number; + additionalServiceAccounts: number; +} + +export const secretsManagerSubscribeFormFactory = ( + formBuilder: FormBuilder +): FormGroup> => + formBuilder.group({ + enabled: [false], + userSeats: [1, [Validators.required, Validators.min(1), Validators.max(100000)]], + additionalServiceAccounts: [ + 0, + [Validators.required, Validators.min(0), Validators.max(100000)], + ], + }); + +@Component({ + selector: "sm-subscribe", + templateUrl: "sm-subscribe.component.html", +}) +export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { + @Input() formGroup: FormGroup>; + @Input() upgradeOrganization: boolean; + @Input() showSubmitButton = false; + @Input() selectedPlan: PlanResponse; + + logo = SecretsManagerLogo; + productTypes = ProductType; + + private destroy$ = new Subject(); + + constructor(private i18nService: I18nService) {} + + ngOnInit() { + this.formGroup.controls.enabled.valueChanges + .pipe(startWith(this.formGroup.value.enabled), takeUntil(this.destroy$)) + .subscribe((enabled) => { + if (enabled) { + this.formGroup.controls.userSeats.enable(); + this.formGroup.controls.additionalServiceAccounts.enable(); + } else { + this.formGroup.controls.userSeats.disable(); + this.formGroup.controls.additionalServiceAccounts.disable(); + } + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + get product() { + return this.selectedPlan.product; + } + + get planName() { + switch (this.product) { + case ProductType.Free: + return this.i18nService.t("free2PersonOrganization"); + case ProductType.Teams: + return this.i18nService.t("planNameTeams"); + case ProductType.Enterprise: + return this.i18nService.t("planNameEnterprise"); + } + } + + get serviceAccountsIncluded() { + return this.selectedPlan.baseServiceAccount; + } + + get monthlyCostPerServiceAccount() { + return this.selectedPlan.isAnnual + ? this.selectedPlan.additionalPricePerServiceAccount / 12 + : this.selectedPlan.additionalPricePerServiceAccount; + } + + get maxUsers() { + return this.selectedPlan.maxUsers; + } + + get maxProjects() { + return this.selectedPlan.maxProjects; + } + + get monthlyCostPerUser() { + return this.selectedPlan.isAnnual + ? this.selectedPlan.seatPrice / 12 + : this.selectedPlan.seatPrice; + } +} diff --git a/apps/web/src/app/billing/settings/organization-plans.component.html b/apps/web/src/app/billing/settings/organization-plans.component.html index a7698f1bf2..dea4e21c9d 100644 --- a/apps/web/src/app/billing/settings/organization-plans.component.html +++ b/apps/web/src/app/billing/settings/organization-plans.component.html @@ -28,7 +28,7 @@ (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate - *ngIf="!loading && !selfHosted && this.plans" + *ngIf="!loading && !selfHosted && this.passwordManagerPlans && this.secretsManagerPlans" class="tw-pt-6" > {{ "freeForever" | i18n }}
-
+

{{ "users" | i18n }}

@@ -230,7 +230,8 @@ {{ "users" | i18n }}: {{ formGroup.controls["additionalSeats"].value || 0 }} × {{ selectablePlan.seatPrice / 12 | currency : "$" }} × 12 - {{ "monthAbbr" | i18n }} = {{ seatTotal(selectablePlan) | currency : "$" }} /{{ + {{ "monthAbbr" | i18n }} = + {{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ "year" | i18n }} @@ -256,7 +257,9 @@ {{ "users" | i18n }}: {{ formGroup.controls["additionalSeats"].value || 0 }} × {{ selectablePlan.seatPrice | currency : "$" }} {{ "monthAbbr" | i18n }} = - {{ seatTotal(selectablePlan) | currency : "$" }} /{{ "month" | i18n }} + {{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ + "month" | i18n + }} {{ "additionalStorageGb" | i18n }}: @@ -268,8 +271,21 @@
-
-

+

+ + +
+ +
+ + +
+

{{ (createOrganization ? "paymentInformation" : "billingInformation") | i18n }}

@@ -279,8 +295,12 @@
- {{ "planPrice" | i18n }}: {{ subtotal | currency : "USD $" }} + {{ "passwordManagerPlanPrice" | i18n }}: {{ passwordManagerSubtotal | currency : "USD $" }}
+ + {{ "secretsManagerPlanPrice" | i18n }}: {{ secretsManagerSubtotal | currency : "USD $" }} +
+
{{ "estimatedTax" | i18n }}: {{ taxCharges | currency : "USD $" }} diff --git a/apps/web/src/app/billing/settings/organization-plans.component.ts b/apps/web/src/app/billing/settings/organization-plans.component.ts index 363f7bc46b..23a38c654a 100644 --- a/apps/web/src/app/billing/settings/organization-plans.component.ts +++ b/apps/web/src/app/billing/settings/organization-plans.component.ts @@ -7,7 +7,7 @@ import { Output, ViewChild, } from "@angular/core"; -import { UntypedFormBuilder, Validators } from "@angular/forms"; +import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; @@ -21,8 +21,11 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; +import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { ProductType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -32,6 +35,8 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { secretsManagerSubscribeFormFactory } from "../organizations/secrets-manager/sm-subscribe.component"; + import { PaymentComponent } from "./payment.component"; import { TaxInfoComponent } from "./tax-info.component"; @@ -82,6 +87,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { singleOrgPolicyAppliesToActiveUser = false; isInTrialFlow = false; discount = 0; + showSecretsManagerSubscribe: boolean; + + secretsManagerSubscription = secretsManagerSubscribeFormFactory(this.formBuilder); formGroup = this.formBuilder.group({ name: [""], @@ -94,9 +102,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { businessName: [""], plan: [this.plan], product: [this.product], + secretsManager: this.secretsManagerSubscription, }); - plans: PlanResponse[]; + passwordManagerPlans: PlanResponse[]; + secretsManagerPlans: PlanResponse[]; private destroy$ = new Subject(); @@ -111,8 +121,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private logService: LogService, private messagingService: MessagingService, - private formBuilder: UntypedFormBuilder, - private organizationApiService: OrganizationApiServiceAbstraction + private formBuilder: FormBuilder, + private organizationApiService: OrganizationApiServiceAbstraction, + private configService: ConfigServiceAbstraction ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -120,7 +131,13 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { async ngOnInit() { if (!this.selfHosted) { const plans = await this.apiService.getPlans(); - this.plans = plans.data; + this.passwordManagerPlans = plans.data.filter( + (plan) => plan.bitwardenProduct === BitwardenProductType.PasswordManager + ); + this.secretsManagerPlans = plans.data.filter( + (plan) => plan.bitwardenProduct === BitwardenProductType.SecretsManager + ); + if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) { this.formGroup.controls.businessOwned.setValue(true); } @@ -131,12 +148,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.changedOwnedBusiness(); } - if (!this.createOrganization || this.acceptingSponsorship) { - this.formGroup.controls.product.setValue(ProductType.Families); - this.changedProduct(); - } - - if (this.createOrganization) { + if (!this.createOrganization) { + this.upgradeFlowPrefillForm(); + } else { this.formGroup.controls.name.addValidators([Validators.required, Validators.maxLength(50)]); this.formGroup.controls.billingEmail.addValidators(Validators.required); } @@ -148,6 +162,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser; }); + this.showSecretsManagerSubscribe = await this.configService.getFeatureFlagBool( + FeatureFlag.SecretsManagerBilling, + false + ); + this.loading = false; } @@ -165,7 +184,15 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } get selectedPlan() { - return this.plans.find((plan) => plan.type === this.formGroup.controls.plan.value); + return this.passwordManagerPlans.find( + (plan) => plan.type === this.formGroup.controls.plan.value + ); + } + + get selectedSecretsManagerPlan() { + return this.secretsManagerPlans.find( + (plan) => plan.type === this.formGroup.controls.plan.value + ); } get selectedPlanInterval() { @@ -173,7 +200,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } get selectableProducts() { - let validPlans = this.plans.filter((plan) => plan.type !== PlanType.Custom); + let validPlans = this.passwordManagerPlans.filter((plan) => plan.type !== PlanType.Custom); if (this.formGroup.controls.businessOwned.value) { validPlans = validPlans.filter((plan) => plan.canBeUsedByBusiness); @@ -191,7 +218,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); if (this.acceptingSponsorship) { - const familyPlan = this.plans.find((plan) => plan.type === PlanType.FamiliesAnnually); + const familyPlan = this.passwordManagerPlans.find( + (plan) => plan.type === PlanType.FamiliesAnnually + ); this.discount = familyPlan.basePrice; validPlans = [familyPlan]; } @@ -200,7 +229,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } get selectablePlans() { - return this.plans?.filter( + return this.passwordManagerPlans?.filter( (plan) => !plan.legacyYear && !plan.disabled && plan.product === this.formGroup.controls.product.value ); @@ -231,21 +260,32 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); } - seatTotal(plan: PlanResponse): number { + seatTotal(plan: PlanResponse, seats: number): number { if (!plan.hasAdditionalSeatsOption) { return 0; } - return plan.seatPrice * Math.abs(this.formGroup.controls.additionalSeats.value || 0); + return plan.seatPrice * Math.abs(seats || 0); } - get subtotal() { + additionalServiceAccountTotal(plan: PlanResponse): number { + if (!plan.hasAdditionalServiceAccountOption) { + return 0; + } + + return ( + plan.additionalPricePerServiceAccount * + Math.abs(this.secretsManagerForm.value.additionalServiceAccounts || 0) + ); + } + + get passwordManagerSubtotal() { let subTotal = this.selectedPlan.basePrice; if ( this.selectedPlan.hasAdditionalSeatsOption && this.formGroup.controls.additionalSeats.value ) { - subTotal += this.seatTotal(this.selectedPlan); + subTotal += this.seatTotal(this.selectedPlan, this.formGroup.value.additionalSeats); } if ( this.selectedPlan.hasAdditionalStorageOption && @@ -262,18 +302,39 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return subTotal - this.discount; } + get secretsManagerSubtotal() { + const plan = this.selectedSecretsManagerPlan; + const formValues = this.secretsManagerForm.value; + + if (!this.planOffersSecretsManager || !formValues.enabled) { + return 0; + } + + let subTotal = plan.basePrice; + if (plan.hasAdditionalSeatsOption && formValues.userSeats) { + subTotal += this.seatTotal(plan, formValues.userSeats); + } + + if (plan.hasAdditionalStorageOption && formValues.additionalServiceAccounts) { + subTotal += this.additionalServiceAccountTotal(this.selectedPlan); + } + + return subTotal; + } + get freeTrial() { return this.selectedPlan.trialPeriodDays != null; } get taxCharges() { return this.taxComponent != null && this.taxComponent.taxRate != null - ? (this.taxComponent.taxRate / 100) * this.subtotal + ? (this.taxComponent.taxRate / 100) * + (this.passwordManagerSubtotal + this.secretsManagerSubtotal) : 0; } get total() { - return this.subtotal + this.taxCharges || 0; + return this.passwordManagerSubtotal + this.secretsManagerSubtotal + this.taxCharges || 0; } get paymentDesc() { @@ -286,6 +347,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } + get secretsManagerForm() { + return this.formGroup.controls.secretsManager; + } + + get planOffersSecretsManager() { + return this.selectedSecretsManagerPlan != null; + } + changedProduct() { this.formGroup.controls.plan.setValue(this.selectablePlans[0].type); if (!this.selectedPlan.hasPremiumAccessOption) { @@ -303,6 +372,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ) { this.formGroup.controls.additionalSeats.setValue(1); } + + if (this.planOffersSecretsManager) { + this.secretsManagerForm.enable(); + } else { + this.secretsManagerForm.disable(); + } + + this.secretsManagerForm.updateValueAndValidity(); } changedOwnedBusiness() { @@ -407,6 +484,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { request.billingAddressCountry = this.taxComponent.taxInfo.country; request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode; + // Secrets Manager + this.buildSecretsManagerRequest(request); + // Retrieve org info to backfill pub/priv key if necessary const org = await this.organizationService.get(this.organizationId); if (!org.hasPublicAndPrivateKeys) { @@ -462,6 +542,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } + // Secrets Manager + this.buildSecretsManagerRequest(request); + if (this.providerId) { const providerRequest = new ProviderOrganizationCreateRequest( this.formGroup.controls.clientOwnerEmail.value, @@ -517,4 +600,40 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return text; } + + private buildSecretsManagerRequest( + request: OrganizationCreateRequest | OrganizationUpgradeRequest + ): void { + const formValues = this.secretsManagerForm.value; + + request.useSecretsManager = this.planOffersSecretsManager && formValues.enabled; + + if (!request.useSecretsManager) { + return; + } + + if (this.selectedSecretsManagerPlan.hasAdditionalSeatsOption) { + request.additionalSmSeats = formValues.userSeats; + } + + if (this.selectedSecretsManagerPlan.hasAdditionalServiceAccountOption) { + request.additionalServiceAccounts = formValues.additionalServiceAccounts; + } + } + + private upgradeFlowPrefillForm() { + if (this.acceptingSponsorship) { + this.formGroup.controls.product.setValue(ProductType.Families); + this.changedProduct(); + return; + } + + // If they already have SM enabled, bump them up to Teams and enable SM to maintain this access + const organization = this.organizationService.get(this.organizationId); + if (organization.useSecretsManager) { + this.formGroup.controls.product.setValue(ProductType.Teams); + this.secretsManagerForm.controls.enabled.setValue(true); + this.changedProduct(); + } + } } diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index c17cf5c76b..cf9a43915a 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -50,6 +50,7 @@ import { UpdatePasswordComponent } from "../auth/update-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; +import { SecretsManagerBillingModule } from "../billing/organizations/secrets-manager/sm-billing.module"; import { AddCreditComponent } from "../billing/settings/add-credit.component"; import { AdjustPaymentComponent } from "../billing/settings/adjust-payment.component"; import { BillingHistoryViewComponent } from "../billing/settings/billing-history-view.component"; @@ -123,6 +124,9 @@ import { SharedModule } from "./shared.module"; ChangeKdfModule, DynamicAvatarComponent, AccountFingerprintComponent, + + // To be removed when OrganizationPlansComponent is moved to its own module (see AC-1453) + SecretsManagerBillingModule, ], declarations: [ PremiumBadgeComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 72b3165e03..330937ecd9 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6906,6 +6906,82 @@ "removeMembersWithoutMasterPasswordWarning": { "message": "Removing members who do not have master passwords without setting one for them may restrict access to their full account." }, + "secretsManagerForPlan": { + "message": "Secrets Manager for $PLAN$", + "placeholders": { + "plan": { + "content": "$1", + "example": "Teams" + } + } + }, + "secretsManagerForPlanDesc": { + "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle." + }, + "free2PersonOrganization": { + "message": "Free 2-person Organizations" + }, + "unlimitedSecrets": { + "message": "Unlimited secrets" + }, + "unlimitedProjects": { + "message": "Unlimited projects" + }, + "projectsIncluded": { + "message": "$COUNT$ projects included", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "serviceAccountsIncluded": { + "message": "$COUNT$ service accounts included", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "additionalServiceAccountCost": { + "message": "$COST$ per month for additional service accounts", + "placeholders": { + "cost": { + "content": "$1", + "example": "$0.50" + } + } + }, + "addSecretsManager": { + "message": "Add Secrets Manager" + }, + "addSecretsManagerUpgradeDesc": { + "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan." + }, + "additionalServiceAccounts": { + "message": "Additional service accounts" + }, + "additionalServiceAccountsDesc": { + "message": "Your plan comes with $COUNT$ service accounts. You can add additional service accounts for $COST$ per month.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + }, + "cost": { + "content": "$2", + "example": "$0.50" + } + } + }, + "passwordManagerPlanPrice": { + "message": "Password Manager plan price" + }, + "secretsManagerPlanPrice": { + "message": "Secrets Manager plan price" + }, "passwordManager": { "message": "Password Manager" }, @@ -6913,3 +6989,4 @@ "message": "Free Organization" } } + diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 93f4de2ffd..c6ce62f232 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -6,6 +6,7 @@ import { OrganizationSsoResponse } from "../../../auth/models/response/organizat import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; +import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; @@ -16,7 +17,6 @@ import { StorageRequest } from "../../../models/request/storage.request"; import { VerifyBankRequest } from "../../../models/request/verify-bank.request"; import { ListResponse } from "../../../models/response/list.response"; import { OrganizationApiKeyType } from "../../enums"; -import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request"; import { OrganizationCreateRequest } from "../../models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; @@ -37,7 +37,10 @@ export class OrganizationApiServiceAbstraction { save: (id: string, request: OrganizationUpdateRequest) => Promise; updatePayment: (id: string, request: PaymentRequest) => Promise; upgrade: (id: string, request: OrganizationUpgradeRequest) => Promise; - updateSubscription: (id: string, request: OrganizationSubscriptionUpdateRequest) => Promise; + updatePasswordManagerSeats: ( + id: string, + request: OrganizationSubscriptionUpdateRequest + ) => Promise; updateSeats: (id: string, request: SeatRequest) => Promise; updateStorage: (id: string, request: StorageRequest) => Promise; verifyBank: (id: string, request: VerifyBankRequest) => Promise; @@ -60,8 +63,5 @@ export class OrganizationApiServiceAbstraction { getSso: (id: string) => Promise; updateSso: (id: string, request: OrganizationSsoRequest) => Promise; selfHostedSyncLicense: (id: string) => Promise; - updateEnrollSecretsManager: ( - id: string, - request: OrganizationEnrollSecretsManagerRequest - ) => Promise; + subscribeToSecretsManager: (id: string, request: SecretsManagerSubscribeRequest) => Promise; } diff --git a/libs/common/src/admin-console/models/request/organization-create.request.ts b/libs/common/src/admin-console/models/request/organization-create.request.ts index 616c37c00c..729cf45365 100644 --- a/libs/common/src/admin-console/models/request/organization-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization-create.request.ts @@ -23,4 +23,8 @@ export class OrganizationCreateRequest { billingAddressState: string; billingAddressPostalCode: string; billingAddressCountry: string; + + useSecretsManager: boolean; + additionalSmSeats: number; + additionalServiceAccounts: number; } diff --git a/libs/common/src/admin-console/models/request/organization-upgrade.request.ts b/libs/common/src/admin-console/models/request/organization-upgrade.request.ts index bf0eb5f47f..eba897f31b 100644 --- a/libs/common/src/admin-console/models/request/organization-upgrade.request.ts +++ b/libs/common/src/admin-console/models/request/organization-upgrade.request.ts @@ -11,4 +11,8 @@ export class OrganizationUpgradeRequest { billingAddressCountry: string; billingAddressPostalCode: string; keys: OrganizationKeysRequest; + + useSecretsManager: boolean; + additionalSmSeats: number; + additionalServiceAccounts: number; } diff --git a/libs/common/src/admin-console/models/request/organization/organization-enroll-secrets-manager.request.ts b/libs/common/src/admin-console/models/request/organization/organization-enroll-secrets-manager.request.ts deleted file mode 100644 index a213b07bba..0000000000 --- a/libs/common/src/admin-console/models/request/organization/organization-enroll-secrets-manager.request.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class OrganizationEnrollSecretsManagerRequest { - enabled: boolean; -} diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index 2c056ee287..6bd339a64d 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -13,6 +13,7 @@ export class OrganizationResponse extends BaseResponse { businessTaxNumber: string; billingEmail: string; plan: PlanResponse; + secretsManagerPlan: PlanResponse; planType: PlanType; seats: number; maxAutoscaleSeats: number; @@ -39,8 +40,14 @@ export class OrganizationResponse extends BaseResponse { this.businessCountry = this.getResponseProperty("BusinessCountry"); this.businessTaxNumber = this.getResponseProperty("BusinessTaxNumber"); this.billingEmail = this.getResponseProperty("BillingEmail"); + const plan = this.getResponseProperty("Plan"); this.plan = plan == null ? null : new PlanResponse(plan); + + const secretsManagerPlan = this.getResponseProperty("SecretsManagerPlan"); + this.secretsManagerPlan = + secretsManagerPlan == null ? null : new PlanResponse(secretsManagerPlan); + this.planType = this.getResponseProperty("PlanType"); this.seats = this.getResponseProperty("Seats"); this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats"); diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index 3a1d355524..503aeb3820 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -7,6 +7,7 @@ import { OrganizationSsoResponse } from "../../../auth/models/response/organizat import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; +import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; @@ -19,7 +20,6 @@ import { ListResponse } from "../../../models/response/list.response"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { OrganizationApiServiceAbstraction } from "../../abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiKeyType } from "../../enums"; -import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request"; import { OrganizationCreateRequest } from "../../models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; @@ -120,7 +120,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction return new PaymentResponse(r); } - async updateSubscription( + async updatePasswordManagerSeats( id: string, request: OrganizationSubscriptionUpdateRequest ): Promise { @@ -294,13 +294,16 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction ); } - async updateEnrollSecretsManager(id: string, request: OrganizationEnrollSecretsManagerRequest) { - await this.apiService.send( + async subscribeToSecretsManager( + id: string, + request: SecretsManagerSubscribeRequest + ): Promise { + return await this.apiService.send( "POST", - "/organizations/" + id + "/enroll-secrets-manager", + "/organizations/" + id + "/subscribe-secrets-manager", request, true, - true + false ); } } diff --git a/libs/common/src/billing/enums/bitwarden-product-type.ts b/libs/common/src/billing/enums/bitwarden-product-type.ts new file mode 100644 index 0000000000..76b0899fd9 --- /dev/null +++ b/libs/common/src/billing/enums/bitwarden-product-type.ts @@ -0,0 +1,4 @@ +export enum BitwardenProductType { + PasswordManager = 0, + SecretsManager = 1, +} diff --git a/libs/common/src/billing/models/request/sm-subscribe.request.ts b/libs/common/src/billing/models/request/sm-subscribe.request.ts new file mode 100644 index 0000000000..581d3007c8 --- /dev/null +++ b/libs/common/src/billing/models/request/sm-subscribe.request.ts @@ -0,0 +1,4 @@ +export class SecretsManagerSubscribeRequest { + additionalSmSeats: number; + additionalServiceAccounts: number; +} diff --git a/libs/common/src/billing/models/response/plan.response.ts b/libs/common/src/billing/models/response/plan.response.ts index 8368849b86..e67ed35e44 100644 --- a/libs/common/src/billing/models/response/plan.response.ts +++ b/libs/common/src/billing/models/response/plan.response.ts @@ -1,10 +1,12 @@ import { ProductType } from "../../../enums"; import { BaseResponse } from "../../../models/response/base.response"; import { PlanType } from "../../enums"; +import { BitwardenProductType } from "../../enums/bitwarden-product-type"; export class PlanResponse extends BaseResponse { type: PlanType; product: ProductType; + bitwardenProduct: BitwardenProductType; name: string; isAnnual: boolean; nameLocalizationKey: string; @@ -48,6 +50,13 @@ export class PlanResponse extends BaseResponse { additionalStoragePricePerGb: number; premiumAccessOptionPrice: number; + // SM only + additionalPricePerServiceAccount: number; + baseServiceAccount: number; + maxServiceAccount: number; + hasAdditionalServiceAccountOption: boolean; + maxProjects: number; + constructor(response: any) { super(response); this.type = this.getResponseProperty("Type"); @@ -90,5 +99,16 @@ export class PlanResponse extends BaseResponse { this.seatPrice = this.getResponseProperty("SeatPrice"); this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb"); this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice"); + + this.bitwardenProduct = this.getResponseProperty("BitwardenProduct"); + this.additionalPricePerServiceAccount = this.getResponseProperty( + "AdditionalPricePerServiceAccount" + ); + this.baseServiceAccount = this.getResponseProperty("BaseServiceAccount"); + this.maxServiceAccount = this.getResponseProperty("MaxServiceAccount"); + this.hasAdditionalServiceAccountOption = this.getResponseProperty( + "HasAdditionalServiceAccountOption" + ); + this.maxProjects = this.getResponseProperty("MaxProjects"); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index e8a05911b9..fb155f54e2 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -2,4 +2,5 @@ export enum FeatureFlag { DisplayEuEnvironmentFlag = "display-eu-environment", DisplayLowKdfIterationWarningFlag = "display-kdf-iteration-warning", TrustedDeviceEncryption = "trusted-device-encryption", + SecretsManagerBilling = "sm-ga-billing", } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index ad7c134889..9c098632b1 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -881,7 +881,7 @@ export class ApiService implements ApiServiceAbstraction { // Plan APIs async getPlans(): Promise> { - const r = await this.send("GET", "/plans/", null, false, true); + const r = await this.send("GET", "/plans/all", null, false, true); return new ListResponse(r, PlanResponse); } From bc85a1ac129719435eb080d5019450bc17d11397 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 05:38:58 +0000 Subject: [PATCH 13/20] Autosync the updated translations (#5663) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/eo/messages.json | 26 +++++++++++------------ apps/desktop/src/locales/fi/messages.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index b230e75302..1ece91c1e1 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -6,13 +6,13 @@ "message": "Filtriloj" }, "allItems": { - "message": "All items" + "message": "Ĉiuj Eroj" }, "favorites": { - "message": "Favorites" + "message": "Plej ŝatataj" }, "types": { - "message": "Types" + "message": "Tipoj" }, "typeLogin": { "message": "Saluto" @@ -21,7 +21,7 @@ "message": "Karto" }, "typeIdentity": { - "message": "Identity" + "message": "Idento" }, "typeSecureNote": { "message": "Sekura noto" @@ -30,10 +30,10 @@ "message": "Dosierujoj" }, "collections": { - "message": "Collections" + "message": "Kolektoj" }, "searchVault": { - "message": "Search vault" + "message": "Traserĉu trezorejon" }, "addItem": { "message": "Aldoni elementon" @@ -45,10 +45,10 @@ "message": "Kundividi" }, "moveToOrganization": { - "message": "Move to organization" + "message": "Movu al organizo" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ moviĝis al $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -61,7 +61,7 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "Elektu organizon kun kiu vi volas dividi ĉi tiun eron. Dividado transdonas posedon de la ero al la organizo. Vi ne plu estos la rekta posedanto de ĉi tiu ero post kiam ĝi estos dividita." }, "attachments": { "message": "Aldonaĵoj" @@ -104,10 +104,10 @@ "message": "Retpoŝta adreso" }, "verificationCodeTotp": { - "message": "Verification code (TOTP)" + "message": "Kontrola kodo (TOTP)" }, "website": { - "message": "Website" + "message": "Retejo" }, "notes": { "message": "Notoj" @@ -116,10 +116,10 @@ "message": "Propraj kampoj" }, "launch": { - "message": "Launch" + "message": "Lanĉo" }, "copyValue": { - "message": "Copy value", + "message": "Kopii valoron", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 96b7282f13..4372bf8635 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -962,7 +962,7 @@ "message": "Käynnistä automaattisesti kirjauduttaessa" }, "openAtLoginDesc": { - "message": "Käynnistä sovellus automaattisesti kirjautumisen yhteydessä." + "message": "Käynnistä Bitwarden-sovellus automaattisesti kirjautumisen yhteydessä." }, "alwaysShowDock": { "message": "Näytä aina Dockissa" From 61c3176917f9b14ed2e0989d727af100aaf4a4cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 05:44:35 +0000 Subject: [PATCH 14/20] Autosync the updated translations (#5664) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/da/messages.json | 4 +- apps/web/src/locales/fi/messages.json | 4 +- apps/web/src/locales/fr/messages.json | 8 +- apps/web/src/locales/it/messages.json | 4 +- apps/web/src/locales/pt_PT/messages.json | 182 +++++++++++------------ 5 files changed, 101 insertions(+), 101 deletions(-) diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 0ae0562ced..4ca36d6c24 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -658,7 +658,7 @@ "message": "Hovedadgangskoden er den adgangskode, du bruger, når du tilgår din boks. Det er meget vigtigt, at hovedadgangskoden ikke glemmes, da der ikke er nogen måde, hvorpå den kan genoprettes." }, "masterPassImportant": { - "message": "Hovedadgangskoder kan ikke gendannes, hvis du glemmer dem!" + "message": "Hovedadgangskoden kan ikke gendannes, hvis den glemmes!" }, "masterPassHintDesc": { "message": "Et hovedadgangskodetip kan bidrage til at komme i tanke om adgangskoden, hvis den glemmes." @@ -5231,7 +5231,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { - "message": "require SSO authentication and single organization policies", + "message": "kræver SSO-godkendelse samt enkeltorganisationspolitikker", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 2302822a16..8642902010 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -6553,7 +6553,7 @@ "message": "Laskutuksen synkronoinnin ohje" }, "licensePaidFeaturesHelp": { - "message": "Maksullisen lisenssin oiminaisuusopas" + "message": "Maksullisen lisenssin ominaisuusopas" }, "selfHostGracePeriodHelp": { "message": "Kun tilauksesi päättyy, sinulla on 60 päivää aikaa päivittää organisaatiosi lisenssitiedosto ajan tasalle. Varoaika päättyy $GRACE_PERIOD_END_DATE$.", @@ -6830,7 +6830,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'" }, "notFound": { - "message": "$RESOURCE$ ei löytynyt", + "message": "Resurssia $RESOURCE$ ei löytynyt", "placeholders": { "resource": { "content": "$1", diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index dcac82ba59..e6b92524ae 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -574,7 +574,7 @@ "message": "Êtes-vous sûr de vouloir écraser le mot de passe actuel ?" }, "editedFolder": { - "message": "Dossier modifié" + "message": "Dossier enregistré" }, "addedFolder": { "message": "Dossier ajouté" @@ -634,7 +634,7 @@ "message": "Commencer la Période d'Essai" }, "logIn": { - "message": "S'identifier" + "message": "Se connecter" }, "logInInitiated": { "message": "Connexion initiée" @@ -676,7 +676,7 @@ "message": "Paramètres" }, "passwordHint": { - "message": "Indice du mot de passe" + "message": "Indice de mot de passe" }, "enterEmailToGetHint": { "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." @@ -6900,6 +6900,6 @@ "message": "Aucun mot de passe principal" }, "removeMembersWithoutMasterPasswordWarning": { - "message": "La suppression des membres qui n'ont pas de mot de passe principal sans leur en définir un, peut restreindre l'accès à leur compte dans soin intégralité." + "message": "La suppression des membres qui n'ont pas de mot de passe principal sans leur en définir un, peut restreindre l'accès à leur compte dans son intégralité." } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index c2c81e9ef6..df18adc0e4 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -6849,7 +6849,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing." }, "deviceApprovals": { - "message": "Approvazioni dispositivi" + "message": "Approva dispositivi" }, "deviceApprovalsDesc": { "message": "Approva le richieste di accesso qui sotto per consentire ai membri di completare l'accesso. Le richieste non approvate scadono dopo 1 settimana. Verifica le informazioni del membro prima di approvare." @@ -6870,7 +6870,7 @@ "message": "Approva richiesta" }, "noDeviceRequests": { - "message": "Nessuna richiesta dispositivo" + "message": "Nessuna richiesta da approvare" }, "noDeviceRequestsDesc": { "message": "Le richieste di approvazione dei dispositivi dei membri appariranno qui" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 673ae54a9c..4c6654245a 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1001,7 +1001,7 @@ "message": "Confirmar a palavra-passe do ficheiro" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Utilize a chave de encriptação da sua conta, derivada do nome de utilizador e da palavra-passe mestra da sua conta, para encriptar a exportação e restringir a importação apenas à conta Bitwarden atual." }, "passwordProtectedOptionDescription": { "message": "Defina uma palavra-passe do ficheiro para encriptar a exportação e importe-a para qualquer conta Bitwarden utilizando a palavra-passe de desencriptação." @@ -1256,7 +1256,7 @@ "message": "Dados importados com sucesso" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Foi importado um total de $AMOUNT$ itens.", "placeholders": { "amount": { "content": "$1", @@ -1919,7 +1919,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "message": "Adquira uma conta Premium por apenas $PRICE$ /ano, ou obtenha contas Premium para $FAMILYPLANUSERCOUNT$ utilizadores e partilha familiar ilimitada com um ", "placeholders": { "price": { "content": "$1", @@ -1932,7 +1932,7 @@ } }, "bitwardenFamiliesPlan": { - "message": "Bitwarden Families plan." + "message": "plano Familiar do Bitwarden." }, "addons": { "message": "Addons" @@ -2029,7 +2029,7 @@ "message": "Cancelar subscrição" }, "subscriptionExpiration": { - "message": "Subscription expiration" + "message": "Validade da subscrição" }, "subscriptionCanceled": { "message": "A subscrição foi cancelada." @@ -2265,7 +2265,7 @@ } }, "planNameFamilies": { - "message": "Famílias" + "message": "Familiar" }, "planDescFamilies": { "message": "Para uso pessoal, para partilhar com a família e amigos." @@ -2472,7 +2472,7 @@ "message": "Tem a certeza de que pretende eliminar este grupo?" }, "deleteMultipleGroupsConfirmation": { - "message": "Are you sure you want to delete the following $QUANTITY$ group(s)?", + "message": "Tem a certeza de que pretende eliminar o(s) seguinte(s) $QUANTITY$ grupo(s)?", "placeholders": { "quantity": { "content": "$1", @@ -2532,7 +2532,7 @@ "message": "Editar membro" }, "fieldOnTabRequiresAttention": { - "message": "A field on the '$TAB$' tab requires your attention.", + "message": "Um campo no separador '$TAB$' precisa da sua atenção.", "placeholders": { "tab": { "content": "$1", @@ -2823,7 +2823,7 @@ } }, "deletedCollections": { - "message": "Deleted collections" + "message": "Coleções eliminadas" }, "deletedCollectionId": { "message": "Coleção $ID$ eliminada.", @@ -2871,7 +2871,7 @@ } }, "deletedManyGroups": { - "message": "Deleted $QUANTITY$ group(s).", + "message": "$QUANTITY$ grupo(s) eliminados.", "placeholders": { "quantity": { "content": "$1", @@ -3363,7 +3363,7 @@ } }, "subscriptionFreePlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "Não pode convidar mais do que $COUNT$ membros sem atualizar o seu plano.", "placeholders": { "count": { "content": "$1", @@ -3372,7 +3372,7 @@ } }, "subscriptionFamiliesPlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan. Please contact Customer Support to upgrade.", + "message": "Não pode convidar mais do que $COUNT$ membros sem atualizar o seu plano. Contacte o Apoio ao cliente para atualizar.", "placeholders": { "count": { "content": "$1", @@ -3381,7 +3381,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "message": "A sua subscrição permite um total de $COUNT$ membros. O seu plano é patrocinado e faturado por uma organização externa.", "placeholders": { "count": { "content": "$1", @@ -5443,7 +5443,7 @@ "message": "Histórico de faturação" }, "backToReports": { - "message": "Back to reports" + "message": "Voltar aos relatórios" }, "organizationPicker": { "message": "Organization picker" @@ -5503,7 +5503,7 @@ "message": "Palavra aleatória" }, "service": { - "message": "Service" + "message": "Serviço" }, "unknownCipher": { "message": "Item desconhecido, poderá ser necessário pedir autorização para aceder a este item." @@ -5961,7 +5961,7 @@ "message": "A eliminação de contas de serviço é permanente e irreversível." }, "deleteServiceAccountsConfirmMessage": { - "message": "Delete $COUNT$ service accounts", + "message": "Eliminar $COUNT$ contas de serviço", "placeholders": { "count": { "content": "$1", @@ -5970,13 +5970,13 @@ } }, "deleteServiceAccountToast": { - "message": "Service account deleted" + "message": "Conta de serviço eliminada" }, "deleteServiceAccountsToast": { - "message": "Service accounts deleted" + "message": "Contas de serviço eliminadas" }, "searchServiceAccounts": { - "message": "Search service accounts", + "message": "Procurar contas de serviço", "description": "Placeholder text for searching service accounts." }, "editServiceAccount": { @@ -6071,7 +6071,7 @@ } }, "deleteProjectInputLabel": { - "message": "Type \"$CONFIRM$\" to continue", + "message": "Escreva \"$CONFIRM$\" para continuar", "description": "Users are prompted to type 'confirm' to delete a project", "placeholders": { "confirm": { @@ -6081,7 +6081,7 @@ } }, "deleteProjectConfirmMessage": { - "message": "Delete $PROJECT$", + "message": "Eliminar $PROJECT$", "description": "Confirmation prompt to delete a specific project, where '$PROJECT$' is a placeholder for the name of the project.", "placeholders": { "project": { @@ -6091,7 +6091,7 @@ } }, "deleteProjectsConfirmMessage": { - "message": "Delete $COUNT$ Projects", + "message": "Eliminar $COUNT$ projetos", "description": "Confirmation prompt to delete multiple projects, where '$COUNT$' is a placeholder for the number of projects to be deleted.", "placeholders": { "count": { @@ -6113,22 +6113,22 @@ "description": "Message to be displayed when there are no projects to display in the list." }, "smConfirmationRequired": { - "message": "Confirmation required", + "message": "Confirmação necessária", "description": "Indicates that user confirmation is required for an action to proceed." }, "bulkDeleteProjectsErrorMessage": { - "message": "The following projects could not be deleted:", + "message": "Os seguintes projetos não puderam ser eliminados:", "description": "Message to be displayed when there is an error during bulk project deletion." }, "softDeleteSuccessToast": { - "message": "Secret sent to trash", + "message": "Segredo movido para o lixo", "description": "Notification to be displayed when a secret is successfully sent to the trash." }, "hardDeleteSuccessToast": { - "message": "Secret permanently deleted" + "message": "Segredo eliminado permanentemente" }, "accessTokens": { - "message": "Access tokens", + "message": "Tokens de acesso", "description": "Title for the section displaying access tokens." }, "newAccessToken": { @@ -6136,7 +6136,7 @@ "description": "Button label for creating a new access token." }, "expires": { - "message": "Expires", + "message": "Expira a", "description": "Label for the expiration date of an access token." }, "canRead": { @@ -6144,11 +6144,11 @@ "description": "Label for the access level of an access token (Read only)." }, "accessTokensNoItemsTitle": { - "message": "No access tokens to show", + "message": "Sem tokens de acesso para mostrar", "description": "Title to be displayed when there are no access tokens to display in the list." }, "accessTokensNoItemsDesc": { - "message": "To get started, create an access token", + "message": "Para começar, crie um token de acesso", "description": "Message to be displayed when there are no access tokens to display in the list." }, "downloadAccessToken": { @@ -6156,7 +6156,7 @@ "description": "Message to be displayed before closing an access token, reminding the user to download or copy it." }, "expiresOnAccessToken": { - "message": "Expires on:", + "message": "Expira a:", "description": "Label for the expiration date of an access token." }, "accessTokenCallOutTitle": { @@ -6164,15 +6164,15 @@ "description": "Notification to inform the user that access tokens are only displayed once and cannot be retrieved again." }, "copyToken": { - "message": "Copy token", + "message": "Copiar token", "description": "Copies the generated access token to the user's clipboard." }, "accessToken": { - "message": "Access token", + "message": "Token de acesso", "description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets." }, "accessTokenExpirationRequired": { - "message": "Expiration date required", + "message": "Data de validade necessária", "description": "Error message indicating that an expiration date for the access token must be set." }, "accessTokenCreatedAndCopied": { @@ -6226,7 +6226,7 @@ } }, "groupInfo": { - "message": "Group info" + "message": "Informações do grupo" }, "editGroupMembersDesc": { "message": "Grant members access to the group's assigned collections." @@ -6241,22 +6241,22 @@ "message": "If checked, this will replace all other collection permissions." }, "selectMembers": { - "message": "Select members" + "message": "Selecionar membros" }, "selectCollections": { - "message": "Select collections" + "message": "Selecionar coleções" }, "role": { "message": "Função" }, "removeMember": { - "message": "Remove member" + "message": "Remover membro" }, "collection": { - "message": "Collection" + "message": "Coleção" }, "noCollection": { - "message": "No collection" + "message": "Sem coleções" }, "canView": { "message": "Pode ver" @@ -6271,7 +6271,7 @@ "message": "Pode editar, excepto palavras-passe" }, "noCollectionsAdded": { - "message": "No collections added" + "message": "Sem coleções adicionadas" }, "noMembersAdded": { "message": "No members added" @@ -6364,28 +6364,28 @@ } }, "domainStatusVerified": { - "message": "Verified" + "message": "Verificado" }, "domainStatusUnverified": { - "message": "Unverified" + "message": "Não verificado" }, "domainNameTh": { - "message": "Name" + "message": "Nome" }, "domainStatusTh": { - "message": "Status" + "message": "Estado" }, "lastChecked": { "message": "Last checked" }, "editDomain": { - "message": "Edit domain" + "message": "Editar domínio" }, "domainFormInvalid": { "message": "There are form errors that need your attention" }, "addedDomain": { - "message": "Added domain $DOMAIN$", + "message": "Domínio $DOMAIN$ adicionado", "placeholders": { "DOMAIN": { "content": "$1", @@ -6421,16 +6421,16 @@ } }, "membersColumnHeader": { - "message": "Member/Group" + "message": "Membro/Grupo" }, "groupAndMemberColumnHeader": { - "message": "Member" + "message": "Membro" }, "selectGroupsAndMembers": { - "message": "Select groups and members" + "message": "Selecionar grupos e membros" }, "selectGroups": { - "message": "Select groups" + "message": "Selecionar grupos" }, "userPermissionOverrideHelper": { "message": "Permissions set for a member will replace permissions set by that member's group" @@ -6439,13 +6439,13 @@ "message": "No members or groups added" }, "deleted": { - "message": "Deleted" + "message": "Eliminado" }, "memberStatusFilter": { "message": "Member status filter" }, "inviteMember": { - "message": "Invite member" + "message": "Convidar membro" }, "needsConfirmation": { "message": "Needs confirmation" @@ -6496,10 +6496,10 @@ } }, "server": { - "message": "Server" + "message": "Servidor" }, "exportData": { - "message": "Export data" + "message": "Exportar dados" }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" @@ -6619,13 +6619,13 @@ "message": "This user can access the Secrets Manager Beta" }, "important": { - "message": "Important:" + "message": "Importante:" }, "viewAll": { - "message": "View all" + "message": "Ver tudo" }, "showingPortionOfTotal": { - "message": "Showing $PORTION$ of $TOTAL$", + "message": "A mostrar $PORTION$ de $TOTAL$", "placeholders": { "portion": { "content": "$1", @@ -6638,16 +6638,16 @@ } }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "Resolva os erros abaixo e tente novamente." }, "description": { - "message": "Description" + "message": "Descrição" }, "errorReadingImportFile": { - "message": "An error occurred when trying to read the import file" + "message": "Ocorreu um erro ao tentar ler o ficheiro de importação" }, "accessedSecret": { - "message": "Accessed secret $SECRET_ID$.", + "message": "Segredo $SECRET_ID$ acedido.", "placeholders": { "secret_id": { "content": "$1", @@ -6660,29 +6660,29 @@ "description": "Software Development Kit" }, "createSecret": { - "message": "Create a secret" + "message": "Criar um segredo" }, "createProject": { - "message": "Create a project" + "message": "Criar um projeto" }, "createServiceAccount": { - "message": "Create a service account" + "message": "Criar uma conta de serviço" }, "downloadThe": { - "message": "Download the", + "message": "Descarregar o", "description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI" }, "smCLI": { - "message": "Secrets Manager CLI" + "message": "gestor de segredos CLI" }, "importSecrets": { - "message": "Import secrets" + "message": "Importar segredos" }, "getStarted": { - "message": "Get started" + "message": "Começar" }, "complete": { - "message": "$COMPLETED$/$TOTAL$ Complete", + "message": "$COMPLETED$/$TOTAL$ concluídos", "placeholders": { "COMPLETED": { "content": "$1", @@ -6695,25 +6695,25 @@ } }, "restoreSecret": { - "message": "Restore secret" + "message": "Restaurar segredo" }, "restoreSecrets": { - "message": "Restore secrets" + "message": "Restaurar segredos" }, "restoreSecretPrompt": { - "message": "Are you sure you want to restore this secret?" + "message": "Tem a certeza de que quer recuperar este segredo?" }, "restoreSecretsPrompt": { - "message": "Are you sure you want to restore these secrets?" + "message": "Tem a certeza de que quer recuperar estes segredos?" }, "secretRestoredSuccessToast": { - "message": "Secret restored" + "message": "Segredo restaurado" }, "secretsRestoredSuccessToast": { - "message": "Secrets restored" + "message": "Segredos restaurados" }, "selectionIsRequired": { - "message": "Selection is required." + "message": "É necessária uma seleção." }, "secretsManagerSubscriptionDesc": { "message": "Turn on organization access to the Secrets Manager at no charge during the Beta program. Users can be granted access to the Beta in Members." @@ -6740,25 +6740,25 @@ "message": "This action will remove your access to the service account." }, "removeAccess": { - "message": "Remove access" + "message": "Remover acesso" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "Palavra-passe mestra exposta" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "Palavra-passe encontrada numa violação de dados. Utilize uma palavra-passe única para proteger a sua conta. Tem a certeza de que pretende utilizar uma palavra-passe exposta?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "Palavra-passe mestra fraca e exposta" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "$LENGTH$ caracteres no mínimo", "placeholders": { "length": { "content": "$1", @@ -6783,14 +6783,14 @@ "message": "Dispensar" }, "notAvailableForFreeOrganization": { - "message": "This feature is not available for free organizations. Contact your organization owner to upgrade." + "message": "Esta funcionalidade não está disponível para organizações gratuitas. Contacte o proprietário da organização para atualizar." }, "smProjectSecretsNoItemsNoAccess": { - "message": "Contact your organization's admin to manage secrets for this project.", + "message": "Contacte o administrador da sua organização para gerir os segredos deste projeto.", "description": "The message shown to the user under a project's secrets tab when the user only has read access to the project." }, "enforceOnLoginDesc": { - "message": "Require existing members to change their passwords" + "message": "Exigir que os membros existentes alterem as suas palavras-passe" }, "region": { "message": "Região" @@ -6804,18 +6804,18 @@ "description": "United States" }, "smProjectDeleteAccessRestricted": { - "message": "You don't have permissions to delete this project", + "message": "Não tem permissões para eliminar este projeto", "description": "The individual description shown to the user when the user doesn't have access to delete a project." }, "smProjectsDeleteBulkConfirmation": { - "message": "The following projects can not be deleted. Would you like to continue?", + "message": "Os seguintes projetos não podem ser eliminados. Gostaria de continuar?", "description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects." }, "updateKdfSettings": { - "message": "Update KDF settings" + "message": "Atualizar definições KDF" }, "trustedDeviceEncryption": { - "message": "Trusted device encryption" + "message": "Encriptação de dispositivo de confiança" }, "memberDecryptionTdeDescriptionStart": { "message": "Uma vez autenticados, os membros desencriptam os dados do cofre utilizando uma chave armazenada no seu dispositivo. A", From c31504cab4df09ae1b1c521b6949e88c7ffbcb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bispo?= Date: Fri, 23 Jun 2023 17:21:48 +0100 Subject: [PATCH 15/20] [PM-1593] send password as null if it is not in state (#5634) --- libs/common/src/auth/services/auth.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 56f601da56..7164b0ae4f 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -304,13 +304,16 @@ export class AuthService implements AuthServiceAbstraction { ).encKey, pubKey.buffer ); - const encryptedMasterPassword = await this.cryptoService.rsaEncrypt( - Utils.fromUtf8ToArray(await this.stateService.getKeyHash()), - pubKey.buffer - ); + let encryptedMasterPassword = null; + if ((await this.stateService.getKeyHash()) != null) { + encryptedMasterPassword = await this.cryptoService.rsaEncrypt( + Utils.fromUtf8ToArray(await this.stateService.getKeyHash()), + pubKey.buffer + ); + } const request = new PasswordlessAuthRequest( encryptedKey.encryptedString, - encryptedMasterPassword.encryptedString, + encryptedMasterPassword?.encryptedString, await this.appIdService.getAppId(), requestApproved ); From e0894182fd0d9aa5b73a71d5cf73f190d25a2f1e Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Fri, 23 Jun 2023 09:37:54 -0700 Subject: [PATCH 16/20] Update web configuration for new build artifact (#5667) --- apps/web/config/euprd.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/config/euprd.json b/apps/web/config/euprd.json index 3813074b7c..db3568acc6 100644 --- a/apps/web/config/euprd.json +++ b/apps/web/config/euprd.json @@ -1,8 +1,8 @@ { "urls": { "icons": "https://icons.bitwarden.net", - "notifications": "https://notifications.bitwarden.net", - "scim": "https://scim.bitwarden.net" + "notifications": "https://notifications.beta.bitwarden.net", + "scim": "https://scim.beta.bitwarden.net" }, "flags": { "secretsManager": true, From a22c77d795d71248a563431788694b1f578007a4 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:05:51 -0700 Subject: [PATCH 17/20] Devops 1421 test dns change take two (#5668) * Update web configuration for new build artifact * Switch to another top level domain --- apps/web/config/euprd.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/config/euprd.json b/apps/web/config/euprd.json index db3568acc6..576b5b4cfa 100644 --- a/apps/web/config/euprd.json +++ b/apps/web/config/euprd.json @@ -1,8 +1,8 @@ { "urls": { "icons": "https://icons.bitwarden.net", - "notifications": "https://notifications.beta.bitwarden.net", - "scim": "https://scim.beta.bitwarden.net" + "notifications": "https://notifications.bitwarden.eu", + "scim": "https://scim.bitwarden.eu" }, "flags": { "secretsManager": true, From 2a63008e828d0ccf6f19a8bf7b3f9d1cab9c20f4 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:38:32 -0400 Subject: [PATCH 18/20] Add step to empty container in Storage Container (#5685) --- .github/workflows/deploy-eu-prod-web.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-eu-prod-web.yml b/.github/workflows/deploy-eu-prod-web.yml index 55cc10825b..f650f08a9b 100644 --- a/.github/workflows/deploy-eu-prod-web.yml +++ b/.github/workflows/deploy-eu-prod-web.yml @@ -42,12 +42,19 @@ jobs: working-directory: apps/web run: unzip ${{ env._WEB_ARTIFACT }} + - name: Empty container in Storage Account + run: | + az storage blob delete-batch \ + --source '$web' \ + --pattern '*' \ + --connection-string "${{ steps.retrieve-secrets.outputs.sa-bitwarden-web-vault-dev-key-temp }}" + - name: Deploy to Azure Storage Account working-directory: apps/web run: | - az storage blob upload-batch --source "./build" \ + az storage blob upload-batch \ + --source "./build" \ --destination '$web' \ - --account-name "bwwebvault1itgprod" \ --connection-string "${{ steps.retrieve-secrets.outputs.sa-bitwarden-web-vault-dev-key-temp }}" \ --overwrite \ --no-progress From 4d62849d01728297c4d774fa1faf6d92f59f84c3 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 26 Jun 2023 17:59:52 -0400 Subject: [PATCH 19/20] Add default code owners (#5687) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d0526b1a79..cc8eb66289 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # The following owners will be the default owners for everything in the repo. # Unless a later match takes precedence -# @bitwarden/team-leads +@bitwarden/team-leads ## Secrets Manager team files ## bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev From d814010bd3c01b09c6f086557487c49736121ca7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 00:13:41 +0200 Subject: [PATCH 20/20] Update chromaui/action digest to 44caff7 (#5590) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/chromatic.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index d2f1f45c25..11be51aaa9 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -37,7 +37,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@a2ed440e22f7d4e2c6b0710f7903aa2df70a1ecd + uses: chromaui/action@44caff7e88d584b04f79f04e31e819f9a95d4d8f with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}