diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 6809d950c4e..5e333b3b58a 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -13,6 +13,7 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' push: branches: - 'main' @@ -25,6 +26,7 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' workflow_dispatch: inputs: {} diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index c9b230fe18c..aa62194af5c 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -77,7 +77,9 @@ type OverlayBackgroundExtensionMessageHandlers = { updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; unlockCompleted: ({ message }: BackgroundMessageParam) => void; + addedCipher: () => void; addEditCipherSubmitted: () => void; + editedCipher: () => void; deletedCipher: () => void; }; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index c469eb7dbbc..df4867640f4 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1000,29 +1000,23 @@ describe("OverlayBackground", () => { }); }); - describe("addEditCipherSubmitted message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "addEditCipherSubmitted", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); + describe("extension messages that trigger an update of the inline menu ciphers", () => { + const extensionMessages = [ + "addedCipher", + "addEditCipherSubmitted", + "editedCipher", + "deletedCipher", + ]; - sendMockExtensionMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + beforeEach(() => { + jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation(); }); - }); - describe("deletedCipher message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "deletedCipher", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); - - sendMockExtensionMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + extensionMessages.forEach((message) => { + it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => { + sendMockExtensionMessage({ command: message }); + expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled(); + }); }); }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 56f6a3a2158..bf954c3419f 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -72,7 +72,9 @@ class OverlayBackground implements OverlayBackgroundInterface { updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), unlockCompleted: ({ message }) => this.unlockCompleted(message), + addedCipher: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(), + editedCipher: () => this.updateOverlayCiphers(), deletedCipher: () => this.updateOverlayCiphers(), }; private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = { diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html index ece6be4c63e..b3dcd626ae3 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -1,6 +1,6 @@ -
-
+
+
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html index 6136db59f46..afb7dd40066 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -14,7 +14,7 @@ *ngIf="collections.length" formControlName="collection" placeholderIcon="bwi-collection" - [placeholderText]="'collections' | i18n" + [placeholderText]="'collection' | i18n" [options]="collections" > @@ -32,7 +32,7 @@ diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index bc42e7cb0a5..69a7039e2ac 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -139,22 +139,22 @@ export class VaultPopupListFiltersService { readonly cipherTypes: ChipSelectOption[] = [ { value: CipherType.Login, - label: this.i18nService.t("logins"), + label: this.i18nService.t("typeLogin"), icon: "bwi-globe", }, { value: CipherType.Card, - label: this.i18nService.t("cards"), + label: this.i18nService.t("typeCard"), icon: "bwi-credit-card", }, { value: CipherType.Identity, - label: this.i18nService.t("identities"), + label: this.i18nService.t("typeIdentity"), icon: "bwi-id-card", }, { value: CipherType.SecureNote, - label: this.i18nService.t("notes"), + label: this.i18nService.t("note"), icon: "bwi-sticky-note", }, ]; diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index e0311beb247..597b388a05b 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -83,6 +83,11 @@ export class Program extends BaseProgram { }); program.on("--help", () => { + writeLn( + chalk.yellowBright( + "\n Tip: Managing and retrieving secrets for dev environments is easier with Bitwarden Secrets Manager. Learn more under https://bitwarden.com/products/secrets-manager/", + ), + ); writeLn("\n Examples:"); writeLn(""); writeLn(" bw login"); diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index e14bb62a35d..12c051271e1 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -97,19 +97,15 @@ export abstract class BaseEventsComponent { this.loading = true; let events: EventView[] = []; let promise: Promise; - try { - promise = this.loadAndParseEvents( - dates[0], - dates[1], - clearExisting ? null : this.continuationToken, - ); + promise = this.loadAndParseEvents( + dates[0], + dates[1], + clearExisting ? null : this.continuationToken, + ); - const result = await promise; - this.continuationToken = result.continuationToken; - events = result.events; - } catch (e) { - this.logService.error(`Handled exception: ${e}`); - } + const result = await promise; + this.continuationToken = result.continuationToken; + events = result.events; if (!clearExisting && this.events != null && this.events.length > 0) { this.events = this.events.concat(events); diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html index 063a8759e6f..69b81ae5571 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html @@ -1,7 +1,5 @@
- - {{ "editPolicy" | i18n }} - {{ policy.name | i18n }} - +
+
- -

+

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

-
-
-
-

{{ "emergencyAccess" | i18n }}

-
-
-

- {{ name }} -

-

{{ "acceptEmergencyAccess" | i18n }}

-
- -
-
-
+
+

+ {{ name }} +

+

{{ "acceptEmergencyAccess" | i18n }}

+
+
diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html new file mode 100644 index 00000000000..fd65192beea --- /dev/null +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html @@ -0,0 +1,61 @@ + + +
+ + {{ "loading" | i18n }} +
+ + {{ error }} + +

{{ "pickAnAvatarColor" | i18n }}

+
+ + + + + + + + + + +
+
+ + + + +
diff --git a/apps/web/src/app/auth/settings/account/change-avatar.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts similarity index 78% rename from apps/web/src/app/auth/settings/account/change-avatar.component.ts rename to apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index bbcbaf6820f..6946f8b94bb 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -1,11 +1,10 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, ElementRef, - EventEmitter, - Input, + Inject, OnDestroy, OnInit, - Output, ViewChild, ViewEncapsulation, } from "@angular/core"; @@ -14,20 +13,20 @@ import { BehaviorSubject, debounceTime, firstValueFrom, Subject, takeUntil } fro import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { DialogService } from "@bitwarden/components"; + +type ChangeAvatarDialogData = { + profile: ProfileResponse; +}; @Component({ - selector: "app-change-avatar", - templateUrl: "change-avatar.component.html", + templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, }) -export class ChangeAvatarComponent implements OnInit, OnDestroy { - @Input() profile: ProfileResponse; - - @Output() changeColor: EventEmitter = new EventEmitter(); - @Output() onSaved = new EventEmitter(); +export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { + profile: ProfileResponse; @ViewChild("colorPicker") colorPickerElement: ElementRef; @@ -52,11 +51,14 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); constructor( + @Inject(DIALOG_DATA) protected data: ChangeAvatarDialogData, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private logService: LogService, private avatarService: AvatarService, - ) {} + private dialogRef: DialogRef, + ) { + this.profile = data.profile; + } async ngOnInit() { //localize the default colors @@ -88,20 +90,15 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { Utils.stringToColor(this.profile.name.toString()); } - async submit() { - try { - if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) { - await this.avatarService.setAvatarColor(this.currentSelection); - this.changeColor.emit(this.currentSelection); - this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated")); - } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); - } - } catch (e) { - this.logService.error(e); + submit = async () => { + if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) { + await this.avatarService.setAvatarColor(this.currentSelection); + this.dialogRef.close(); + this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated")); + } else { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } - } + }; async ngOnDestroy() { this.destroy$.next(); @@ -131,6 +128,10 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { } } } + + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(ChangeAvatarDialogComponent, config); + } } export class NamedAvatarColor { diff --git a/apps/web/src/app/auth/settings/account/change-avatar.component.html b/apps/web/src/app/auth/settings/account/change-avatar.component.html deleted file mode 100644 index 3a974241d5f..00000000000 --- a/apps/web/src/app/auth/settings/account/change-avatar.component.html +++ /dev/null @@ -1,84 +0,0 @@ - - - diff --git a/apps/web/src/app/auth/settings/account/profile.component.html b/apps/web/src/app/auth/settings/account/profile.component.html index db6ab2b6588..4464824c63e 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -23,6 +23,7 @@ - diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index 64c5687c0b8..33463b689c9 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -1,28 +1,25 @@ -import { ViewChild, ViewContainerRef, Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { DialogService } from "@bitwarden/components"; -import { ChangeAvatarComponent } from "./change-avatar.component"; +import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", }) -export class ProfileComponent implements OnInit, OnDestroy { +export class ProfileComponent implements OnInit { loading = true; profile: ProfileResponse; fingerprintMaterial: string; - - @ViewChild("avatarModalTemplate", { read: ViewContainerRef, static: true }) - avatarModalRef: ViewContainerRef; private destroy$ = new Subject(); protected formGroup = new FormGroup({ @@ -35,7 +32,7 @@ export class ProfileComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private stateService: StateService, - private modalService: ModalService, + private dialogService: DialogService, ) {} async ngOnInit() { @@ -53,24 +50,17 @@ export class ProfileComponent implements OnInit, OnDestroy { }); } + openChangeAvatar = async () => { + ChangeAvatarDialogComponent.open(this.dialogService, { + data: { profile: this.profile }, + }); + }; + async ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } - openChangeAvatar = async () => { - const modalOpened = await this.modalService.openViewRef( - ChangeAvatarComponent, - this.avatarModalRef, - (modal) => { - modal.profile = this.profile; - modal.changeColor.pipe(takeUntil(this.destroy$)).subscribe(() => { - modalOpened[0].close(); - }); - }, - ); - }; - submit = async () => { const request = new UpdateProfileRequest( this.formGroup.get("name").value, diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html index e17714cca79..a7efaed731e 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html @@ -1,109 +1,80 @@ - +
+ + {{ "twoStepLogin" | i18n }} + {{ "authenticatorAppTitle" | i18n }} + + + + Authenticator app logo +

{{ "twoStepAuthenticatorDesc" | i18n }}

+

+ 1. {{ "twoStepAuthenticatorDownloadApp" | i18n }} +

+
+ + +

{{ "twoStepLoginProviderEnabled" | i18n }}

+ {{ "twoStepAuthenticatorReaddDesc" | i18n }} +
+ Authenticator app logo +

{{ "twoStepAuthenticatorNeedApp" | i18n }}

+
+ +

{{ "twoStepAuthenticatorAppsRecommended" | i18n }}

+

+ 2. {{ "twoStepAuthenticatorScanCode" | i18n }} +

+
+

+
+ {{ key }} +

+ + + 3. {{ "twoStepAuthenticatorEnterCode" | i18n }} + + + +
+ + + + +
+
diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts index 88b695eb728..17cdbb595f7 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts @@ -1,4 +1,6 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -38,15 +40,21 @@ export class TwoFactorAuthenticatorComponent extends TwoFactorBaseComponent implements OnInit, OnDestroy { + @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; - token: string; formPromise: Promise; override componentName = "app-two-factor-authenticator"; private qrScript: HTMLScriptElement; + protected formGroup = new FormGroup({ + token: new FormControl(null, [Validators.required]), + }); + constructor( + @Inject(DIALOG_DATA) protected data: AuthResponse, + private dialogRef: DialogRef, apiService: ApiService, i18nService: I18nService, userVerificationService: UserVerificationService, @@ -68,8 +76,9 @@ export class TwoFactorAuthenticatorComponent this.qrScript.async = true; } - ngOnInit() { + async ngOnInit() { window.document.body.appendChild(this.qrScript); + await this.auth(this.data); } ngOnDestroy() { @@ -81,17 +90,24 @@ export class TwoFactorAuthenticatorComponent return this.processResponse(authResponse.response); } - submit() { + submit = async () => { if (this.enabled) { - return super.disable(this.formPromise); + await this.disableAuthentication(this.formPromise); + this.onChangeStatus.emit(this.enabled); + this.close(); } else { - return this.enable(); + await this.enable(); + this.onChangeStatus.emit(this.enabled); } + }; + + private async disableAuthentication(promise: Promise) { + return super.disable(promise); } protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); - request.token = this.token; + request.token = this.formGroup.value.token; request.key = this.key; return super.enable(async () => { @@ -102,7 +118,7 @@ export class TwoFactorAuthenticatorComponent } private async processResponse(response: TwoFactorAuthenticatorResponse) { - this.token = null; + this.formGroup.get("token").setValue(null); this.enabled = response.enabled; this.key = response.key; const email = await firstValueFrom( @@ -121,4 +137,15 @@ export class TwoFactorAuthenticatorComponent }); }, 100); } + + close = () => { + this.dialogRef.close(this.enabled); + }; + + static open( + dialogService: DialogService, + config: DialogConfig>, + ) { + return dialogService.open(TwoFactorAuthenticatorComponent, config); + } } diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor-setup.component.html index a20bb345664..28baf72f885 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.html @@ -80,7 +80,6 @@ - diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor-setup.component.ts index dd4c69aa277..34a7e32089f 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.ts @@ -34,8 +34,6 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component"; templateUrl: "two-factor-setup.component.html", }) export class TwoFactorSetupComponent implements OnInit, OnDestroy { - @ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true }) - authenticatorModalRef: ViewContainerRef; @ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true }) yubikeyModalRef: ViewContainerRef; @ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef; @@ -137,12 +135,11 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const authComp = await this.openModal( - this.authenticatorModalRef, - TwoFactorAuthenticatorComponent, + const authComp: DialogRef = TwoFactorAuthenticatorComponent.open( + this.dialogService, + { data: result }, ); - await authComp.auth(result); - authComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => { + authComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => { this.updateStatus(enabled, TwoFactorProviderType.Authenticator); }); break; diff --git a/apps/web/src/app/billing/shared/payment.component.html b/apps/web/src/app/billing/shared/payment.component.html index db6f4f311b1..d76de65e507 100644 --- a/apps/web/src/app/billing/shared/payment.component.html +++ b/apps/web/src/app/billing/shared/payment.component.html @@ -1,163 +1,113 @@ -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- -
-
-
- Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay -
-
- -
-
-
-
- - +
+ + + + + {{ "creditCard" | i18n }} - - + + + + + {{ "bankAccount" | i18n }} + + + PayPal + + + + + {{ "accountCredit" | i18n }} + + +
+ +
+
+ +
+
+
+ Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay +
+
+ +
+
+
+
+ + + + +
+
-
-
- - - - {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} - -
-
- - + + + + {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} + +
+ + {{ "routingNumber" | i18n }} + + + + {{ "accountNumber" | i18n }} + + + + {{ "accountHolderName" | i18n }} + + + + + {{ "bankAccountType" | i18n }} + + + + + +
-
- - + + +
+
+ {{ "paypalClickSubmit" | i18n }}
-
- - -
-
- - -
-
-
- -
-
- {{ "paypalClickSubmit" | i18n }} -
-
- - - {{ "makeSureEnoughCredit" | i18n }} - - + + + + {{ "makeSureEnoughCredit" | i18n }} + + +
diff --git a/apps/web/src/app/billing/shared/payment.component.ts b/apps/web/src/app/billing/shared/payment.component.ts index 652bed7801d..95b25a74e4e 100644 --- a/apps/web/src/app/billing/shared/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment.component.ts @@ -1,4 +1,5 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; @@ -17,23 +18,34 @@ import { SharedModule } from "../../shared"; export class PaymentComponent implements OnInit, OnDestroy { @Input() showMethods = true; @Input() showOptions = true; - @Input() method = PaymentMethodType.Card; @Input() hideBank = false; @Input() hidePaypal = false; @Input() hideCredit = false; @Input() trialFlow = false; + @Input() + set method(value: PaymentMethodType) { + this._method = value; + this.paymentForm?.controls.method.setValue(value, { emitEvent: false }); + } + + get method(): PaymentMethodType { + return this._method; + } + private _method: PaymentMethodType = PaymentMethodType.Card; + private destroy$ = new Subject(); - - bank: any = { - routing_number: null, - account_number: null, - account_holder_name: null, - account_holder_type: "", - currency: "USD", - country: "US", - }; - + protected paymentForm = new FormGroup({ + method: new FormControl(this.method), + bank: new FormGroup({ + routing_number: new FormControl(null, [Validators.required]), + account_number: new FormControl(null, [Validators.required]), + account_holder_name: new FormControl(null, [Validators.required]), + account_holder_type: new FormControl("", [Validators.required]), + currency: new FormControl("USD"), + country: new FormControl("US"), + }), + }); paymentMethodType = PaymentMethodType; private btScript: HTMLScriptElement; @@ -85,7 +97,6 @@ export class PaymentComponent implements OnInit, OnDestroy { invalid: "is-invalid", }; } - async ngOnInit() { if (!this.showOptions) { this.hidePaypal = this.method !== PaymentMethodType.PayPal; @@ -97,6 +108,13 @@ export class PaymentComponent implements OnInit, OnDestroy { if (!this.hidePaypal) { window.document.head.appendChild(this.btScript); } + this.paymentForm + .get("method") + .valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((v) => { + this.method = v; + this.changeMethod(); + }); } ngOnDestroy() { @@ -140,7 +158,6 @@ export class PaymentComponent implements OnInit, OnDestroy { changeMethod() { this.btInstance = null; - if (this.method === PaymentMethodType.PayPal) { window.setTimeout(() => { (window as any).braintree.dropin.create( @@ -209,15 +226,17 @@ export class PaymentComponent implements OnInit, OnDestroy { } }); } else { - this.stripe.createToken("bank_account", this.bank).then((result: any) => { - if (result.error) { - reject(result.error.message); - } else if (result.token && result.token.id != null) { - resolve([result.token.id, this.method]); - } else { - reject(); - } - }); + this.stripe + .createToken("bank_account", this.paymentForm.get("bank").value) + .then((result: any) => { + if (result.error) { + reject(result.error.message); + } else if (result.token && result.token.id != null) { + resolve([result.token.id, this.method]); + } else { + reject(); + } + }); } } }); diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 8509e987eb4..77e51172269 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -128,15 +128,6 @@ const routes: Routes = [ component: AcceptOrganizationComponent, data: { titleId: "joinOrganization", doNotSaveUrl: false } satisfies DataProperties, }, - { - path: "accept-emergency", - canActivate: [deepLinkGuard()], - data: { titleId: "acceptEmergency", doNotSaveUrl: false } satisfies DataProperties, - loadComponent: () => - import("./auth/emergency-access/accept/accept-emergency.component").then( - (mod) => mod.AcceptEmergencyComponent, - ), - }, { path: "accept-families-for-enterprise", component: AcceptFamilySponsorshipComponent, @@ -223,6 +214,29 @@ const routes: Routes = [ titleId: "recoverAccountTwoStep", } satisfies DataProperties & AnonLayoutWrapperData, }, + { + path: "accept-emergency", + canActivate: [deepLinkGuard()], + children: [ + { + path: "", + data: { + pageTitle: "emergencyAccess", + titleId: "acceptEmergency", + doNotSaveUrl: false, + } satisfies DataProperties & AnonLayoutWrapperData, + loadComponent: () => + import("./auth/emergency-access/accept/accept-emergency.component").then( + (mod) => mod.AcceptEmergencyComponent, + ), + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, ], }, { diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 79478d0cae6..b8cfbd34013 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -28,7 +28,7 @@ import { RegisterFormModule } from "../auth/register-form/register-form.module"; import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { AccountComponent } from "../auth/settings/account/account.component"; -import { ChangeAvatarComponent } from "../auth/settings/account/change-avatar.component"; +import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component"; import { ChangeEmailComponent } from "../auth/settings/account/change-email.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; @@ -156,7 +156,7 @@ import { SharedModule } from "./shared.module"; PreferencesComponent, PremiumBadgeComponent, ProfileComponent, - ChangeAvatarComponent, + ChangeAvatarDialogComponent, ProvidersComponent, PurgeVaultComponent, RecoverDeleteComponent, @@ -230,7 +230,7 @@ import { SharedModule } from "./shared.module"; PreferencesComponent, PremiumBadgeComponent, ProfileComponent, - ChangeAvatarComponent, + ChangeAvatarDialogComponent, ProvidersComponent, PurgeVaultComponent, RecoverDeleteComponent, diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index eae6dc2a275..a46636532bf 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -3,7 +3,6 @@ import { Observable } from "rxjs"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { MasterKey } from "@bitwarden/common/types/key"; import { @@ -72,12 +71,4 @@ export abstract class LoginStrategyServiceAbstraction { * Creates a master key from the provided master password and email. */ makePreloginKey: (masterPassword: string, email: string) => Promise; - /** - * Sends a response to an auth request. - */ - passwordlessLogin: ( - id: string, - key: string, - requestApproved: boolean, - ) => Promise; } diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index f425bc697c5..7169fd69e93 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -24,8 +24,6 @@ import { PBKDF2KdfConfig, } from "@bitwarden/common/auth/models/domain/kdf-config"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -39,7 +37,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -263,47 +260,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig); } - // TODO: move to auth request service - async passwordlessLogin( - id: string, - key: string, - requestApproved: boolean, - ): Promise { - const pubKey = Utils.fromB64ToArray(key); - - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; - const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); - let keyToEncrypt; - let encryptedMasterKeyHash = null; - - if (masterKey) { - keyToEncrypt = masterKey.encKey; - - // Only encrypt the master password hash if masterKey exists as - // we won't have a masterKeyHash without a masterKey - const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId)); - if (masterKeyHash != null) { - encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt( - Utils.fromUtf8ToArray(masterKeyHash), - pubKey, - ); - } - } else { - const userKey = await this.cryptoService.getUserKey(); - keyToEncrypt = userKey.key; - } - - const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey); - - const request = new PasswordlessAuthRequest( - encryptedKey.encryptedString, - encryptedMasterKeyHash?.encryptedString, - await this.appIdService.getAppId(), - requestApproved, - ); - return await this.apiService.putAuthRequest(id, request); - } - private async clearCache(): Promise { await this.currentAuthnTypeState.update((_) => null); await this.loginStrategyCacheState.update((_) => null); diff --git a/libs/components/src/icon/icons/no-results.ts b/libs/components/src/icon/icons/no-results.ts index f68e67f88ce..02e03f4c4ec 100644 --- a/libs/components/src/icon/icons/no-results.ts +++ b/libs/components/src/icon/icons/no-results.ts @@ -2,17 +2,17 @@ import { svgIcon } from "../icon"; export const NoResults = svgIcon` - - - - - - - - - - - + + + + + + + + + + + `;