mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
Merge remote-tracking branch 'origin/main' into extension-signing
This commit is contained in:
@@ -179,6 +179,18 @@
|
||||
"addItem": {
|
||||
"message": "Add item"
|
||||
},
|
||||
"accountEmail": {
|
||||
"message": "Account email"
|
||||
},
|
||||
"requestHint": {
|
||||
"message": "Request hint"
|
||||
},
|
||||
"requestPasswordHint": {
|
||||
"message": "Request password hint"
|
||||
},
|
||||
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
|
||||
"message": "Enter your account email address and your password hint will be sent to you"
|
||||
},
|
||||
"passwordHint": {
|
||||
"message": "Password hint"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Injectable, NgModule } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
|
||||
import {
|
||||
authGuard,
|
||||
lockGuard,
|
||||
@@ -15,11 +17,13 @@ import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
AnonLayoutWrapperData,
|
||||
PasswordHintComponent,
|
||||
RegistrationFinishComponent,
|
||||
RegistrationStartComponent,
|
||||
RegistrationStartSecondaryComponent,
|
||||
RegistrationStartSecondaryComponentData,
|
||||
SetPasswordJitComponent,
|
||||
UserLockIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@@ -27,6 +31,7 @@ import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-fa
|
||||
import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard";
|
||||
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
|
||||
import { EnvironmentComponent } from "../auth/popup/environment.component";
|
||||
import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
|
||||
import { HintComponent } from "../auth/popup/hint.component";
|
||||
import { HomeComponent } from "../auth/popup/home.component";
|
||||
import { LockComponent } from "../auth/popup/lock.component";
|
||||
@@ -213,12 +218,6 @@ const routes: Routes = [
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides)],
|
||||
data: { state: "register" },
|
||||
},
|
||||
{
|
||||
path: "hint",
|
||||
component: HintComponent,
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides)],
|
||||
data: { state: "hint" },
|
||||
},
|
||||
{
|
||||
path: "environment",
|
||||
component: EnvironmentComponent,
|
||||
@@ -385,6 +384,41 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { state: "update-temp-password" },
|
||||
},
|
||||
...unauthUiRefreshSwap(
|
||||
HintComponent,
|
||||
ExtensionAnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides)],
|
||||
data: {
|
||||
state: "hint",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
children: [
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides)],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageIcon: UserLockIcon,
|
||||
showBackButton: true,
|
||||
state: "hint",
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: PasswordHintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
{
|
||||
path: "",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AvatarModule, ButtonModule, ToastModule } from "@bitwarden/components";
|
||||
import { AccountComponent } from "../auth/popup/account-switching/account.component";
|
||||
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
|
||||
import { EnvironmentComponent } from "../auth/popup/environment.component";
|
||||
import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
|
||||
import { HintComponent } from "../auth/popup/hint.component";
|
||||
import { HomeComponent } from "../auth/popup/home.component";
|
||||
import { LockComponent } from "../auth/popup/lock.component";
|
||||
@@ -131,6 +132,7 @@ import "../platform/popup/locales";
|
||||
HeaderComponent,
|
||||
UserVerificationDialogComponent,
|
||||
CurrentAccountComponent,
|
||||
ExtensionAnonLayoutWrapperComponent,
|
||||
],
|
||||
declarations: [
|
||||
ActionButtonsComponent,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
|
||||
import {
|
||||
authGuard,
|
||||
lockGuard,
|
||||
@@ -12,11 +14,13 @@ import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
AnonLayoutWrapperData,
|
||||
PasswordHintComponent,
|
||||
RegistrationFinishComponent,
|
||||
RegistrationStartComponent,
|
||||
RegistrationStartSecondaryComponent,
|
||||
RegistrationStartSecondaryComponentData,
|
||||
SetPasswordJitComponent,
|
||||
UserLockIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@@ -94,7 +98,6 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{ path: "accessibility-cookie", component: AccessibilityCookieComponent },
|
||||
{ path: "hint", component: HintComponent },
|
||||
{ path: "set-password", component: SetPasswordComponent },
|
||||
{ path: "sso", component: SsoComponent },
|
||||
{
|
||||
@@ -113,6 +116,41 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { titleId: "removeMasterPassword" },
|
||||
},
|
||||
...unauthUiRefreshSwap(
|
||||
HintComponent,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "passwordHint",
|
||||
titleId: "passwordHint",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
children: [
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageIcon: UserLockIcon,
|
||||
state: "hint",
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: PasswordHintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
{
|
||||
path: "",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
|
||||
@@ -194,9 +194,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
break;
|
||||
case "loggedOut":
|
||||
this.modalService.closeAll();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.notificationsService.updateConnection();
|
||||
if (message.userId == null || message.userId === this.activeUserId) {
|
||||
await this.notificationsService.updateConnection();
|
||||
}
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateAppMenu();
|
||||
@@ -694,9 +694,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
|
||||
// This must come last otherwise the logout will prematurely trigger
|
||||
// a process reload before all the state service user data can be cleaned up
|
||||
if (userBeingLoggedOut === activeUserId) {
|
||||
this.authService.logOut(async () => {});
|
||||
}
|
||||
this.authService.logOut(async () => {}, userBeingLoggedOut);
|
||||
}
|
||||
|
||||
private async recordActivity() {
|
||||
|
||||
@@ -560,6 +560,18 @@
|
||||
"settings": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"accountEmail": {
|
||||
"message": "Account email"
|
||||
},
|
||||
"requestHint": {
|
||||
"message": "Request hint"
|
||||
},
|
||||
"requestPasswordHint": {
|
||||
"message": "Request password hint"
|
||||
},
|
||||
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
|
||||
"message": "Enter your account email address and your password hint will be sent to you"
|
||||
},
|
||||
"passwordHint": {
|
||||
"message": "Password hint"
|
||||
},
|
||||
|
||||
@@ -119,9 +119,12 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.notificationsService.updateConnection(false);
|
||||
break;
|
||||
case "loggedOut":
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.notificationsService.updateConnection(false);
|
||||
if (
|
||||
message.userId == null ||
|
||||
message.userId === (await firstValueFrom(this.accountService.activeAccount$))
|
||||
) {
|
||||
await this.notificationsService.updateConnection(false);
|
||||
}
|
||||
break;
|
||||
case "unlocked":
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
@@ -311,7 +314,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
});
|
||||
}, userId);
|
||||
}
|
||||
|
||||
private async recordActivity() {
|
||||
|
||||
@@ -53,10 +53,19 @@
|
||||
[class]="'tw-grid-cols-' + selectableProducts.length"
|
||||
>
|
||||
<div
|
||||
*ngFor="let selectableProduct of selectableProducts; let i = index"
|
||||
*ngFor="
|
||||
let selectableProduct of selectableProducts;
|
||||
trackBy: manageSelectableProduct;
|
||||
let i = index
|
||||
"
|
||||
[ngClass]="getPlanCardContainerClasses(selectableProduct, i)"
|
||||
(click)="selectPlan(selectableProduct)"
|
||||
tabindex="0"
|
||||
[attr.tabindex]="focusedIndex !== i || isCardDisabled(i) ? '-1' : '0'"
|
||||
class="product-card"
|
||||
(keyup)="onKeydown($event, i)"
|
||||
(focus)="onFocus(i)"
|
||||
[attr.aria-disabled]="isCardDisabled(i)"
|
||||
[id]="i + 'a_plan_card'"
|
||||
>
|
||||
<div class="tw-relative">
|
||||
<div
|
||||
|
||||
@@ -160,6 +160,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
showPayment: boolean = false;
|
||||
totalOpened: boolean = false;
|
||||
currentPlan: PlanResponse;
|
||||
currentFocusIndex = 0;
|
||||
isCardStateDisabled = false;
|
||||
focusedIndex: number | null = null;
|
||||
|
||||
deprecateStripeSourcesAPI: boolean;
|
||||
|
||||
@@ -255,6 +258,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setInitialPlanSelection() {
|
||||
this.focusedIndex = this.selectableProducts.length - 1;
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
}
|
||||
|
||||
@@ -307,15 +311,22 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (plan == this.currentPlan) {
|
||||
cardState = PlanCardState.Disabled;
|
||||
this.isCardStateDisabled = true;
|
||||
this.focusedIndex = index;
|
||||
} else if (plan == this.selectedPlan) {
|
||||
cardState = PlanCardState.Selected;
|
||||
this.isCardStateDisabled = false;
|
||||
this.focusedIndex = index;
|
||||
} else if (
|
||||
this.selectedInterval === PlanInterval.Monthly &&
|
||||
plan.productTier == ProductTierType.Families
|
||||
) {
|
||||
cardState = PlanCardState.Disabled;
|
||||
this.isCardStateDisabled = true;
|
||||
this.focusedIndex = this.selectableProducts.length - 1;
|
||||
} else {
|
||||
cardState = PlanCardState.NotSelected;
|
||||
this.isCardStateDisabled = false;
|
||||
}
|
||||
|
||||
switch (cardState) {
|
||||
@@ -466,7 +477,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get storageGb() {
|
||||
return this.sub?.maxStorageGb - 1;
|
||||
return this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0;
|
||||
}
|
||||
|
||||
passwordManagerSeatTotal(plan: PlanResponse): number {
|
||||
@@ -492,7 +503,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
return (
|
||||
plan.PasswordManager.additionalStoragePricePerGb * Math.abs(this.sub?.maxStorageGb - 1 || 0)
|
||||
plan.PasswordManager.additionalStoragePricePerGb *
|
||||
Math.abs(this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0 || 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -861,4 +873,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return this.i18nService.t("planNameTeams");
|
||||
}
|
||||
}
|
||||
|
||||
onKeydown(event: KeyboardEvent, index: number) {
|
||||
const cardElements = Array.from(document.querySelectorAll(".product-card")) as HTMLElement[];
|
||||
let newIndex = index;
|
||||
const direction = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : -1;
|
||||
|
||||
if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
|
||||
do {
|
||||
newIndex = (newIndex + direction + cardElements.length) % cardElements.length;
|
||||
} while (this.isCardDisabled(newIndex) && newIndex !== index);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
setTimeout(() => {
|
||||
const card = cardElements[newIndex];
|
||||
if (
|
||||
!(
|
||||
card.classList.contains("tw-bg-secondary-100") &&
|
||||
card.classList.contains("tw-text-muted")
|
||||
)
|
||||
) {
|
||||
card?.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
onFocus(index: number) {
|
||||
this.focusedIndex = index;
|
||||
this.selectPlan(this.selectableProducts[index]);
|
||||
}
|
||||
|
||||
isCardDisabled(index: number): boolean {
|
||||
const card = this.selectableProducts[index];
|
||||
return card === (this.currentPlan || this.isCardStateDisabled);
|
||||
}
|
||||
|
||||
manageSelectableProduct(index: number) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { Route, RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
|
||||
import {
|
||||
authGuard,
|
||||
lockGuard,
|
||||
@@ -12,13 +13,15 @@ import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
AnonLayoutWrapperData,
|
||||
PasswordHintComponent,
|
||||
RegistrationFinishComponent,
|
||||
RegistrationStartComponent,
|
||||
RegistrationStartSecondaryComponent,
|
||||
RegistrationStartSecondaryComponentData,
|
||||
SetPasswordJitComponent,
|
||||
LockIcon,
|
||||
RegistrationLinkExpiredComponent,
|
||||
LockIcon,
|
||||
UserLockIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@@ -167,6 +170,49 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
...unauthUiRefreshSwap(
|
||||
AnonLayoutWrapperComponent,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "passwordHint",
|
||||
titleId: "passwordHint",
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: HintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
children: [
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageIcon: UserLockIcon,
|
||||
state: "hint",
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: PasswordHintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
{
|
||||
path: "",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
@@ -388,25 +434,6 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "passwordHint",
|
||||
titleId: "passwordHint",
|
||||
} satisfies DataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: HintComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "remove-password",
|
||||
component: RemovePasswordComponent,
|
||||
|
||||
@@ -960,6 +960,18 @@
|
||||
"settings": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"accountEmail": {
|
||||
"message": "Account email"
|
||||
},
|
||||
"requestHint": {
|
||||
"message": "Request hint"
|
||||
},
|
||||
"requestPasswordHint": {
|
||||
"message": "Request password hint"
|
||||
},
|
||||
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
|
||||
"message": "Enter your account email address and your password hint will be sent to you"
|
||||
},
|
||||
"passwordHint": {
|
||||
"message": "Password hint"
|
||||
},
|
||||
@@ -9050,7 +9062,7 @@
|
||||
"message": "Directory integration"
|
||||
},
|
||||
"passwordLessSso": {
|
||||
"message": "PasswordLess SSO"
|
||||
"message": "Passwordless SSO"
|
||||
},
|
||||
"accountRecovery": {
|
||||
"message": "Account recovery"
|
||||
|
||||
@@ -317,6 +317,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
// Try to load from memory first
|
||||
const email = await firstValueFrom(this.loginEmailService.loginEmail$);
|
||||
const rememberEmail = this.loginEmailService.getRememberEmail();
|
||||
|
||||
if (email) {
|
||||
this.formGroup.controls.email.setValue(email);
|
||||
this.formGroup.controls.rememberEmail.setValue(rememberEmail);
|
||||
|
||||
@@ -20,6 +20,7 @@ export function unauthUiRefreshSwap(
|
||||
defaultComponent: Type<any>,
|
||||
refreshedComponent: Type<any>,
|
||||
options: Route,
|
||||
altOptions?: Route,
|
||||
): Routes {
|
||||
return componentRouteSwap(
|
||||
defaultComponent,
|
||||
@@ -29,5 +30,6 @@ export function unauthUiRefreshSwap(
|
||||
return configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh);
|
||||
},
|
||||
options,
|
||||
altOptions,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<!-- The calc() reductions are to account for browser/desktop headers -->
|
||||
<main
|
||||
class="tw-flex tw-min-h-screen tw-w-full tw-mx-auto tw-flex-col tw-gap-7 tw-bg-background-alt tw-px-8 tw-pb-4 tw-text-main"
|
||||
class="tw-flex tw-w-full tw-mx-auto tw-flex-col tw-gap-7 tw-bg-background-alt tw-px-8 tw-pb-4 tw-text-main"
|
||||
[ngClass]="{
|
||||
'tw-pt-0': decreaseTopPadding,
|
||||
'tw-pt-8': !decreaseTopPadding,
|
||||
'tw-relative tw-top-0': clientType === 'browser',
|
||||
'tw-min-h-screen': clientType === 'web',
|
||||
'tw-min-h-[calc(100vh-72px)]': clientType === 'browser',
|
||||
'tw-min-h-[calc(100vh-54px)]': clientType === 'desktop',
|
||||
}"
|
||||
>
|
||||
<bit-icon *ngIf="!hideLogo" [icon]="logo" class="tw-w-[128px] [&>*]:tw-align-top"></bit-icon>
|
||||
@@ -23,6 +26,7 @@
|
||||
{{ title }}
|
||||
</h1>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="subtitle" class="tw-text-sm sm:tw-text-base">{{ subtitle }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./bitwarden-logo.icon";
|
||||
export * from "./bitwarden-shield.icon";
|
||||
export * from "./lock.icon";
|
||||
export * from "./user-lock.icon";
|
||||
export * from "./user-verification-biometrics-fingerprint.icon";
|
||||
|
||||
22
libs/auth/src/angular/icons/user-lock.icon.ts
Normal file
22
libs/auth/src/angular/icons/user-lock.icon.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const UserLockIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="100" fill="none">
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M0 18.207a7.798 7.798 0 0 1 7.798-7.798H89.38a7.798 7.798 0 0 1 7.799 7.798v8.763h-2.4v-8.763a5.399 5.399 0 0 0-5.398-5.399H7.797A5.399 5.399 0 0 0 2.4 18.207v49.19a5.399 5.399 0 0 0 5.4 5.398h9.483v2.4H7.798A7.798 7.798 0 0 1 0 67.396V18.207Zm49.378 54.588h13.498v2.4H49.378v-2.4Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M88.78 58.398c8.946 0 16.197-7.251 16.197-16.196s-7.251-16.197-16.196-16.197-16.197 7.252-16.197 16.197 7.252 16.196 16.197 16.196Zm0 2.4c10.27 0 18.597-8.326 18.597-18.596s-8.326-18.596-18.596-18.596-18.596 8.326-18.596 18.596S78.51 60.798 88.78 60.798Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M61.005 87.192h54.28c1.303 0 1.833-.344 2.01-.53.128-.134.396-.517.226-1.586-2.187-13.74-14.213-24.278-28.752-24.278S62.203 71.337 60.017 85.076c-.09.57.026 1.226.279 1.662.115.2.23.305.316.359.072.046.184.095.393.095Zm0 2.4h54.28c3.346 0 5.104-1.76 4.605-4.893-2.371-14.903-15.402-26.3-31.121-26.3-15.72 0-28.75 11.397-31.122 26.3-.337 2.121.744 4.893 3.358 4.893Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M77.983 17.607a1.2 1.2 0 0 1-1.2-1.2v-.6a1.2 1.2 0 1 1 2.4 0v.6a1.2 1.2 0 0 1-1.2 1.2ZM83.382 17.607a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2ZM88.78 17.607a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M95.083 20.607H0v-1.2h95.083v1.2ZM15.12 54.571a5.999 5.999 0 0 1 6-5.998h23.23a5.999 5.999 0 0 1 6 5.998V57.2h-2.4V54.57a3.6 3.6 0 0 0-3.6-3.599H21.12a3.6 3.6 0 0 0-3.6 3.6v2.627h-2.4V54.57Zm2.4 15.825v2.693a3.6 3.6 0 0 0 3.6 3.599h23.23a3.6 3.6 0 0 0 3.6-3.6v-2.692h2.4v2.693a5.999 5.999 0 0 1-6 5.998H21.12a5.999 5.999 0 0 1-6-5.998v-2.693h2.4Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M32.479 33.255c-5.31 0-9.641 4.332-9.641 9.64v6.822h-2.4v-6.821c0-6.635 5.406-12.04 12.04-12.04 6.633 0 12.041 5.377 12.041 12.04v6.821h-2.4v-6.821c0-5.334-4.33-9.641-9.64-9.641Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M10.498 63.797a7.498 7.498 0 0 1 7.498-7.498h30.593a7.498 7.498 0 0 1 0 14.997H17.996a7.498 7.498 0 0 1-7.498-7.499Zm43.79 0a5.699 5.699 0 0 0-5.699-5.699H17.996a5.699 5.699 0 0 0 0 11.398h30.593a5.699 5.699 0 0 0 5.7-5.699Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M19.02 60.669a.6.6 0 0 1 .6.6v2.959a.6.6 0 0 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M22.443 63.13a.6.6 0 0 1-.388.755l-2.851.914a.6.6 0 1 1-.367-1.142l2.852-.915a.6.6 0 0 1 .754.388Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M18.67 63.742a.6.6 0 0 1 .837.135l1.748 2.42a.6.6 0 0 1-.972.703l-1.749-2.42a.6.6 0 0 1 .136-.838Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M19.368 63.739a.6.6 0 0 1 .142.837l-1.722 2.42a.6.6 0 0 1-.978-.695l1.722-2.42a.6.6 0 0 1 .836-.142Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M15.626 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 0 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755ZM28.651 60.669a.6.6 0 0 1 .6.6v2.959a.6.6 0 1 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M32.046 63.129a.6.6 0 0 1-.386.755l-2.824.915a.6.6 0 1 1-.37-1.142l2.825-.914a.6.6 0 0 1 .755.386Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M28.3 63.742a.6.6 0 0 1 .837.135l1.749 2.42a.6.6 0 0 1-.973.703l-1.748-2.42a.6.6 0 0 1 .135-.838Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M29.002 63.742a.6.6 0 0 1 .136.837L27.387 67a.6.6 0 0 1-.972-.702l1.749-2.421a.6.6 0 0 1 .837-.136Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M25.256 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 1 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755ZM34.857 66.649a.6.6 0 0 1 .6-.6h5.649a.6.6 0 0 1 0 1.2h-5.649a.6.6 0 0 1-.6-.6ZM44.487 66.649a.6.6 0 0 1 .6-.6h5.65a.6.6 0 0 1 0 1.2h-5.65a.6.6 0 0 1-.6-.6Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
`;
|
||||
@@ -16,7 +16,9 @@ export * from "./fingerprint-dialog/fingerprint-dialog.component";
|
||||
|
||||
// password callout
|
||||
export * from "./password-callout/password-callout.component";
|
||||
export * from "./vault-timeout-input/vault-timeout-input.component";
|
||||
|
||||
// password hint
|
||||
export * from "./password-hint/password-hint.component";
|
||||
|
||||
// input password
|
||||
export * from "./input-password/input-password.component";
|
||||
@@ -40,3 +42,6 @@ export * from "./registration/registration-start/registration-start-secondary.co
|
||||
export * from "./registration/registration-env-selector/registration-env-selector.component";
|
||||
export * from "./registration/registration-finish/registration-finish.service";
|
||||
export * from "./registration/registration-finish/default-registration-finish.service";
|
||||
|
||||
// vault timeout
|
||||
export * from "./vault-timeout-input/vault-timeout-input.component";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<form [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<!-- <ng-template> must be within the <form> to ensure that `formControlName` has a parent `formGroup` directive. -->
|
||||
<ng-template #formContentTemplate>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "accountEmail" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
appAutofocus
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
type="email"
|
||||
formControlName="email"
|
||||
/>
|
||||
</bit-form-field>
|
||||
|
||||
<button
|
||||
class="tw-mb-2"
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
[block]="true"
|
||||
>
|
||||
{{ "requestHint" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="cancel()" [block]="true">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<!-- Browser -->
|
||||
<main *ngIf="clientType === 'browser'" tabindex="-1">
|
||||
<ng-container *ngTemplateOutlet="formContentTemplate"></ng-container>
|
||||
</main>
|
||||
|
||||
<!-- Web, Desktop -->
|
||||
<ng-container *ngIf="clientType !== 'browser'">
|
||||
<ng-container *ngTemplateOutlet="formContentTemplate"></ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
107
libs/auth/src/angular/password-hint/password-hint.component.ts
Normal file
107
libs/auth/src/angular/password-hint/password-hint.component.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Router, RouterModule } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/password-hint.request";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./password-hint.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CommonModule,
|
||||
FormFieldModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
],
|
||||
})
|
||||
export class PasswordHintComponent implements OnInit {
|
||||
protected clientType: ClientType;
|
||||
|
||||
protected formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
protected get email() {
|
||||
return this.formGroup.controls.email.value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
private loginEmailService: LoginEmailServiceAbstraction,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
const email = (await firstValueFrom(this.loginEmailService.loginEmail$)) ?? "";
|
||||
this.formGroup.controls.email.setValue(email);
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
const isEmailValid = this.validateEmailOrShowToast(this.email);
|
||||
if (!isEmailValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("masterPassSent"),
|
||||
});
|
||||
|
||||
await this.router.navigate(["login"]);
|
||||
};
|
||||
|
||||
protected async cancel() {
|
||||
this.loginEmailService.setLoginEmail(this.email);
|
||||
await this.router.navigate(["login"]);
|
||||
}
|
||||
|
||||
private validateEmailOrShowToast(email: string): boolean {
|
||||
// If email is null or empty, show error toast and return false
|
||||
if (email == null || email === "") {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("emailRequired"),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// If not a valid email format, show error toast and return false
|
||||
if (email.indexOf("@") === -1) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("invalidEmail"),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // email is valid
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,5 @@ export abstract class AuthService {
|
||||
abstract authStatusFor$(userId: UserId): Observable<AuthenticationStatus>;
|
||||
/** @deprecated use {@link activeAccountStatus$} instead */
|
||||
abstract getAuthStatus: (userId?: string) => Promise<AuthenticationStatus>;
|
||||
abstract logOut: (callback: () => void) => void;
|
||||
abstract logOut: (callback: () => void, userId?: string) => void;
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
return await firstValueFrom(this.authStatusFor$(userId as UserId));
|
||||
}
|
||||
|
||||
logOut(callback: () => void) {
|
||||
logOut(callback: () => void, userId?: string): void {
|
||||
callback();
|
||||
this.messageSender.send("loggedOut");
|
||||
this.messageSender.send("loggedOut", { userId });
|
||||
}
|
||||
}
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -119,7 +119,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "7.16.1",
|
||||
"@typescript-eslint/parser": "7.16.1",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"@yao-pkg/pkg": "5.12.1",
|
||||
"@yao-pkg/pkg": "5.14.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.1.3",
|
||||
"base64-loader": "1.0.0",
|
||||
@@ -10828,16 +10828,16 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@yao-pkg/pkg": {
|
||||
"version": "5.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-5.12.1.tgz",
|
||||
"integrity": "sha512-vqp8Z9o39LDKTpjfeDjJsLf4mi0zS4jkbTTZbptfc/K1KKDU2hosex64TaattPO9NLkibc6EJldmSjVmc63ooA==",
|
||||
"version": "5.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-5.14.0.tgz",
|
||||
"integrity": "sha512-34oflUyAOI64a4cc4AF3ckvS8Qqnk/ISvZ1bDBa1/JAYaaFtzAO+RlhPaU+wCHzhk6VXvZwEywJpb+SlVDTgdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/generator": "7.23.0",
|
||||
"@babel/parser": "7.23.0",
|
||||
"@babel/types": "7.23.0",
|
||||
"@yao-pkg/pkg-fetch": "3.5.9",
|
||||
"@yao-pkg/pkg-fetch": "3.5.11",
|
||||
"chalk": "^4.1.2",
|
||||
"fs-extra": "^9.1.0",
|
||||
"globby": "^11.1.0",
|
||||
@@ -10854,9 +10854,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@yao-pkg/pkg-fetch": {
|
||||
"version": "3.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.9.tgz",
|
||||
"integrity": "sha512-usMwwqFCd2B7k+V87u6kiTesyDSlw+3LpiuYBWe+UgryvSOk/NXjx3XVCub8hQoi0bCREbdQ6NDBqminyHJJrg==",
|
||||
"version": "3.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.11.tgz",
|
||||
"integrity": "sha512-2tQ/1n7BLTptW6lL0pfTCnVMIxls8Jiw0/ClK1J2Fja9z2S2j4uzNL5dwGRqtvPJPn/q9i8X+Y+c4dwnMb+NOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "7.16.1",
|
||||
"@typescript-eslint/parser": "7.16.1",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"@yao-pkg/pkg": "5.12.1",
|
||||
"@yao-pkg/pkg": "5.14.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.1.3",
|
||||
"base64-loader": "1.0.0",
|
||||
|
||||
Reference in New Issue
Block a user