mirror of
https://github.com/bitwarden/browser
synced 2026-03-01 19:11:22 +00:00
Merge branch 'main' into km/pm-18576/fix-missing-userid-on-remove-password
This commit is contained in:
@@ -423,7 +423,7 @@
|
||||
"enableHardwareAccelerationDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showSshAgentOption">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="enableSshAgent">
|
||||
<input
|
||||
|
||||
@@ -22,7 +22,6 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutAction,
|
||||
@@ -67,7 +66,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
showAlwaysShowDock = false;
|
||||
requireEnableTray = false;
|
||||
showDuckDuckGoIntegrationOption = false;
|
||||
showSshAgentOption = false;
|
||||
showOpenAtLoginOption = false;
|
||||
isWindows: boolean;
|
||||
isLinux: boolean;
|
||||
@@ -223,7 +221,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSshAgentOption = await this.configService.getFeatureFlag(FeatureFlag.SSHAgent);
|
||||
this.userHasMasterPassword = await this.userVerificationService.hasMasterPassword();
|
||||
|
||||
this.isWindows = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop;
|
||||
|
||||
@@ -51,17 +51,13 @@ import {
|
||||
|
||||
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
|
||||
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
|
||||
import { HintComponent } from "../auth/hint.component";
|
||||
import { LoginDecryptionOptionsComponentV1 } from "../auth/login/login-decryption-options/login-decryption-options-v1.component";
|
||||
import { LoginComponentV1 } from "../auth/login/login-v1.component";
|
||||
import { LoginViaAuthRequestComponentV1 } from "../auth/login/login-via-auth-request-v1.component";
|
||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||
import { SsoComponentV1 } from "../auth/sso-v1.component";
|
||||
import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
||||
import { VaultComponent } from "../vault/app/vault/vault.component";
|
||||
|
||||
import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
|
||||
/**
|
||||
@@ -167,33 +163,6 @@ const routes: Routes = [
|
||||
},
|
||||
{ path: "accessibility-cookie", component: AccessibilityCookieComponent },
|
||||
{ path: "set-password", component: SetPasswordComponent },
|
||||
...unauthUiRefreshSwap(
|
||||
SsoComponentV1,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "sso",
|
||||
},
|
||||
{
|
||||
path: "sso",
|
||||
data: {
|
||||
pageIcon: VaultIcon,
|
||||
pageTitle: {
|
||||
key: "enterpriseSingleSignOn",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "singleSignOnEnterOrgIdentifierText",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: SsoComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
{
|
||||
path: "send",
|
||||
component: SendComponent,
|
||||
@@ -209,139 +178,10 @@ const routes: Routes = [
|
||||
component: RemovePasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
...unauthUiRefreshSwap(
|
||||
LoginViaAuthRequestComponentV1,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "login-with-device",
|
||||
},
|
||||
{
|
||||
path: "login-with-device",
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
pageTitle: {
|
||||
key: "logInRequestSent",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "aNotificationWasSentToYourDevice",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: LoginViaAuthRequestComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
...unauthUiRefreshSwap(
|
||||
LoginViaAuthRequestComponentV1,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "admin-approval-requested",
|
||||
},
|
||||
{
|
||||
path: "admin-approval-requested",
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
pageTitle: {
|
||||
key: "adminApprovalRequested",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "adminApprovalRequestSentToAdmins",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [{ path: "", component: LoginViaAuthRequestComponent }],
|
||||
},
|
||||
),
|
||||
...unauthUiRefreshSwap(
|
||||
HintComponent,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
children: [
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "requestPasswordHint",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
},
|
||||
pageIcon: UserLockIcon,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: PasswordHintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
...unauthUiRefreshSwap(
|
||||
LoginComponentV1,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "login",
|
||||
component: LoginComponentV1,
|
||||
canActivate: [maxAccountsGuardFn()],
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
children: [
|
||||
{
|
||||
path: "login",
|
||||
canActivate: [maxAccountsGuardFn()],
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "logInToBitwarden",
|
||||
},
|
||||
pageIcon: VaultIcon,
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: LoginComponent },
|
||||
{ path: "", component: LoginSecondaryContentComponent, outlet: "secondary" },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
data: {
|
||||
overlayPosition: DesktopDefaultOverlayPosition,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
...unauthUiRefreshSwap(
|
||||
LoginDecryptionOptionsComponentV1,
|
||||
AnonLayoutWrapperComponent,
|
||||
{
|
||||
path: "login-initiated",
|
||||
canActivate: [tdeDecryptionRequiredGuard()],
|
||||
},
|
||||
{
|
||||
path: "login-initiated",
|
||||
canActivate: [tdeDecryptionRequiredGuard()],
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
},
|
||||
children: [{ path: "", component: LoginDecryptionOptionsComponent }],
|
||||
},
|
||||
),
|
||||
{
|
||||
path: "passkeys",
|
||||
component: Fido2PlaceholderComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
@@ -383,6 +223,110 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "login",
|
||||
canActivate: [maxAccountsGuardFn()],
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "logInToBitwarden",
|
||||
},
|
||||
pageIcon: VaultIcon,
|
||||
},
|
||||
children: [
|
||||
{ path: "", component: LoginComponent },
|
||||
{ path: "", component: LoginSecondaryContentComponent, outlet: "secondary" },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
data: {
|
||||
overlayPosition: DesktopDefaultOverlayPosition,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "login-initiated",
|
||||
canActivate: [tdeDecryptionRequiredGuard()],
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
},
|
||||
children: [{ path: "", component: LoginDecryptionOptionsComponent }],
|
||||
},
|
||||
{
|
||||
path: "sso",
|
||||
data: {
|
||||
pageIcon: VaultIcon,
|
||||
pageTitle: {
|
||||
key: "enterpriseSingleSignOn",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "singleSignOnEnterOrgIdentifierText",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: SsoComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "login-with-device",
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
pageTitle: {
|
||||
key: "logInRequestSent",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "aNotificationWasSentToYourDevice",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: LoginViaAuthRequestComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "admin-approval-requested",
|
||||
data: {
|
||||
pageIcon: DevicesIcon,
|
||||
pageTitle: {
|
||||
key: "adminApprovalRequested",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "adminApprovalRequestSentToAdmins",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [{ path: "", component: LoginViaAuthRequestComponent }],
|
||||
},
|
||||
{
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "requestPasswordHint",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
},
|
||||
pageIcon: UserLockIcon,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: PasswordHintComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "lock",
|
||||
canActivate: [lockGuard()],
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
import { filter, firstValueFrom, map, Subject, takeUntil, timeout, withLatestFrom } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { FingerprintDialogComponent, LoginApprovalComponent } from "@bitwarden/auth/angular";
|
||||
@@ -157,7 +159,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private stateEventRunnerService: StateEventRunnerService,
|
||||
private accountService: AccountService,
|
||||
private organizationService: OrganizationService,
|
||||
) {}
|
||||
private deviceTrustToastService: DeviceTrustToastService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
|
||||
|
||||
@@ -46,8 +46,6 @@ import { HeaderComponent } from "./layout/header.component";
|
||||
import { NavComponent } from "./layout/nav.component";
|
||||
import { SearchComponent } from "./layout/search/search.component";
|
||||
import { SharedModule } from "./shared/shared.module";
|
||||
import { AddEditComponent as SendAddEditComponent } from "./tools/send/add-edit.component";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -60,6 +58,7 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
DeleteAccountComponent,
|
||||
UserVerificationComponent,
|
||||
DecryptionFailureDialogComponent,
|
||||
NavComponent,
|
||||
],
|
||||
declarations: [
|
||||
AccessibilityCookieComponent,
|
||||
@@ -76,13 +75,10 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
FolderAddEditComponent,
|
||||
HeaderComponent,
|
||||
HintComponent,
|
||||
NavComponent,
|
||||
PasswordHistoryComponent,
|
||||
PremiumComponent,
|
||||
RemovePasswordComponent,
|
||||
SearchComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div
|
||||
style="background:white; display:flex; justify-content: center; align-items: center; flex-direction: column"
|
||||
>
|
||||
<h1 style="color: black">Select your passkey</h1>
|
||||
<br />
|
||||
<button
|
||||
style="color:black; padding: 10px 20px; border: 1px solid black; margin: 10px"
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
(click)="closeModal()"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class Fido2PlaceholderComponent {
|
||||
constructor(
|
||||
private readonly desktopSettingsService: DesktopSettingsService,
|
||||
private readonly router: Router,
|
||||
) {}
|
||||
|
||||
async closeModal() {
|
||||
await this.router.navigate(["/"]);
|
||||
await this.desktopSettingsService.setInModalMode(false);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterLink, RouterLinkActive } from "@angular/router";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-nav",
|
||||
templateUrl: "nav.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, RouterLinkActive],
|
||||
})
|
||||
export class NavComponent {
|
||||
items: any[] = [
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
AuthService,
|
||||
AuthService as AuthServiceAbstraction,
|
||||
} from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
@@ -102,6 +103,7 @@ import {
|
||||
BiometricsService,
|
||||
} from "@bitwarden/key-management";
|
||||
import { LockComponentService } from "@bitwarden/key-management-ui";
|
||||
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||
|
||||
import { DesktopLoginApprovalComponentService } from "../../auth/login/desktop-login-approval-component.service";
|
||||
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
|
||||
@@ -366,6 +368,7 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: DesktopSetPasswordJitService,
|
||||
deps: [
|
||||
ApiService,
|
||||
MasterPasswordApiService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
I18nServiceAbstraction,
|
||||
@@ -430,6 +433,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: DesktopLoginApprovalComponentService,
|
||||
deps: [I18nServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SshImportPromptService,
|
||||
useClass: DefaultSshImportPromptService,
|
||||
deps: [DialogService, ToastService, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -16,11 +17,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { CalloutModule, DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-add-edit",
|
||||
templateUrl: "add-edit.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, ReactiveFormsModule, CalloutModule],
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
constructor(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -17,6 +20,7 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service.
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { invokeMenu, RendererMenuItem } from "../../../utils";
|
||||
import { NavComponent } from "../../layout/nav.component";
|
||||
import { SearchBarService } from "../../layout/search/search-bar.service";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
@@ -32,6 +36,8 @@ const BroadcasterSubscriptionId = "SendComponent";
|
||||
@Component({
|
||||
selector: "app-send",
|
||||
templateUrl: "send.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, FormsModule, NavComponent, AddEditComponent],
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
<div id="login-page" class="page-top-padding">
|
||||
<form
|
||||
id="login-page"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="formGroup"
|
||||
attr.aria-hidden="{{ showingModal }}"
|
||||
>
|
||||
<div id="content" class="content" style="padding-top: 50px">
|
||||
<a (click)="invalidateEmail()" class="tw-cursor-pointer">
|
||||
<img class="logo-image" alt="Bitwarden" />
|
||||
</a>
|
||||
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||
<!-- start email -->
|
||||
<ng-container *ngIf="!validatedEmail; else loginPage">
|
||||
<div class="box last">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
formControlName="email"
|
||||
appInputVerbatim="false"
|
||||
(keyup.enter)="continue()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<environment-selector #environmentSelector (onOpenSelfHostedSettings)="settings()">
|
||||
</environment-selector>
|
||||
</div>
|
||||
<div class="checkbox remember-email">
|
||||
<label for="rememberEmail">
|
||||
<input
|
||||
id="rememberEmail"
|
||||
type="checkbox"
|
||||
name="rememberEmail"
|
||||
formControlName="rememberEmail"
|
||||
/>
|
||||
{{ "rememberEmail" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="buttons with-rows">
|
||||
<div class="buttons-row">
|
||||
<button type="button" class="btn primary block" (click)="continue()">
|
||||
{{ "continue" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-options">
|
||||
<p class="no-margin">{{ "newAroundHere" | i18n }}</p>
|
||||
<button type="button" class="text text-primary" routerLink="/signup">
|
||||
{{ "createAccount" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template [formGroup]="formGroup" #loginPage>
|
||||
<div class="box last">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
class="monospaced"
|
||||
formControlName="masterPassword"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box last" [hidden]="!showCaptcha()">
|
||||
<div class="box-content">
|
||||
<iframe
|
||||
id="hcaptcha_iframe"
|
||||
style="margin-top: 20px"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
></iframe>
|
||||
<div class="box-content-row">
|
||||
<button
|
||||
class="btn block"
|
||||
type="button"
|
||||
routerLink="/accessibility-cookie"
|
||||
(click)="saveEmailSettings()"
|
||||
>
|
||||
<i class="bwi bwi-universal-access" aria-hidden="true"></i>
|
||||
{{ "loadAccessibilityCookie" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons with-rows">
|
||||
<div class="buttons-row">
|
||||
<button type="submit" class="btn primary block" [disabled]="form.loading">
|
||||
<b [hidden]="form.loading"
|
||||
><i class="bwi bwi-sign-in" aria-hidden="true"></i>
|
||||
{{ "loginWithMasterPassword" | i18n }}</b
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons-row" *ngIf="showLoginWithDevice">
|
||||
<button type="button" class="btn block" (click)="startAuthRequestLogin()">
|
||||
<i class="bwi bwi-mobile" aria-hidden="true"></i>
|
||||
{{ "logInWithAnotherDevice" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons-row">
|
||||
<button
|
||||
type="button"
|
||||
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
|
||||
class="btn block"
|
||||
>
|
||||
<i class="bwi bwi-provider" aria-hidden="true"></i>
|
||||
{{ "enterpriseSingleSignOn" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-options">
|
||||
<button
|
||||
type="button"
|
||||
class="text text-primary password-hint-btn"
|
||||
routerLink="/hint"
|
||||
(click)="saveEmailSettings()"
|
||||
>
|
||||
{{ "getMasterPasswordHint" | i18n }}
|
||||
</button>
|
||||
<div>
|
||||
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ loggedEmail }}</p>
|
||||
<a [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<ng-template #environment></ng-template>
|
||||
@@ -1,266 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
|
||||
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
import { EnvironmentComponent } from "../environment.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "LoginComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-login",
|
||||
templateUrl: "login-v1.component.html",
|
||||
})
|
||||
export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||
environmentModal: ViewContainerRef;
|
||||
|
||||
protected componentDestroyed$: Subject<void> = new Subject();
|
||||
webVaultHostname = "";
|
||||
|
||||
showingModal = false;
|
||||
|
||||
private deferFocus: boolean = null;
|
||||
|
||||
get loggedEmail() {
|
||||
return this.formGroup.value.email;
|
||||
}
|
||||
|
||||
constructor(
|
||||
devicesApiService: DevicesApiServiceAbstraction,
|
||||
appIdService: AppIdService,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
syncService: SyncService,
|
||||
private modalService: ModalService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
stateService: StateService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone,
|
||||
private messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
formBuilder: FormBuilder,
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
route: ActivatedRoute,
|
||||
loginEmailService: LoginEmailServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
devicesApiService,
|
||||
appIdService,
|
||||
loginStrategyService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
stateService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
cryptoFunctionService,
|
||||
logService,
|
||||
ngZone,
|
||||
formBuilder,
|
||||
formValidationErrorService,
|
||||
route,
|
||||
loginEmailService,
|
||||
ssoLoginService,
|
||||
toastService,
|
||||
);
|
||||
this.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.listenForUnauthUiRefreshFlagChanges();
|
||||
|
||||
await super.ngOnInit();
|
||||
await this.getLoginWithDevice(this.loggedEmail);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
this.ngZone.run(() => {
|
||||
switch (message.command) {
|
||||
case "windowHidden":
|
||||
this.onWindowHidden();
|
||||
break;
|
||||
case "windowIsFocused":
|
||||
if (this.deferFocus === null) {
|
||||
this.deferFocus = !message.windowIsFocused;
|
||||
if (!this.deferFocus) {
|
||||
this.focusInput();
|
||||
}
|
||||
} else if (this.deferFocus && message.windowIsFocused) {
|
||||
this.focusInput();
|
||||
this.deferFocus = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
this.messagingService.send("getWindowIsFocused");
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
this.componentDestroyed$.next();
|
||||
this.componentDestroyed$.complete();
|
||||
}
|
||||
|
||||
private listenForUnauthUiRefreshFlagChanges() {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
|
||||
.pipe(
|
||||
tap(async (flag) => {
|
||||
if (flag) {
|
||||
const qParams = await firstValueFrom(this.route.queryParams);
|
||||
|
||||
const uniqueQueryParams = {
|
||||
...qParams,
|
||||
// adding a unique timestamp to the query params to force a reload
|
||||
t: new Date().getTime().toString(),
|
||||
};
|
||||
|
||||
await this.router.navigate(["/"], {
|
||||
queryParams: uniqueQueryParams,
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.componentDestroyed$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async settings() {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
EnvironmentComponent,
|
||||
this.environmentModal,
|
||||
);
|
||||
|
||||
modal.onShown.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
|
||||
this.showingModal = true;
|
||||
});
|
||||
|
||||
modal.onClosed.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
|
||||
this.showingModal = false;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => {
|
||||
modal.close();
|
||||
await this.getLoginWithDevice(this.loggedEmail);
|
||||
});
|
||||
}
|
||||
|
||||
onWindowHidden() {
|
||||
this.showPassword = false;
|
||||
}
|
||||
|
||||
async continue() {
|
||||
await super.validateEmail();
|
||||
if (!this.formGroup.controls.email.valid) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccured"),
|
||||
message: this.i18nService.t("invalidEmail"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.focusInput();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.validatedEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
if (this.captchaSiteKey) {
|
||||
const content = document.getElementById("content") as HTMLDivElement;
|
||||
content.setAttribute("style", "width:335px");
|
||||
}
|
||||
}
|
||||
|
||||
private focusInput() {
|
||||
const email = this.loggedEmail;
|
||||
document.getElementById(email == null || email === "" ? "email" : "masterPassword")?.focus();
|
||||
}
|
||||
|
||||
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
|
||||
if (!ipc.platform.isAppImage && !ipc.platform.isSnapStore && !ipc.platform.isDev) {
|
||||
return super.launchSsoBrowser(clientId, ssoRedirectUri);
|
||||
}
|
||||
const email = this.formGroup.controls.email.value;
|
||||
|
||||
// Save off email for SSO
|
||||
await this.ssoLoginService.setSsoEmail(email);
|
||||
|
||||
// Generate necessary sso params
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
length: 64,
|
||||
uppercase: true,
|
||||
lowercase: true,
|
||||
numbers: true,
|
||||
special: false,
|
||||
};
|
||||
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
|
||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||
|
||||
// Save sso params
|
||||
await this.ssoLoginService.setSsoState(state);
|
||||
await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier);
|
||||
|
||||
try {
|
||||
await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state, email);
|
||||
// FIXME: Remove when updating file. Eslint update
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccured"),
|
||||
this.i18nService.t("ssoError"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the validatedEmail flag to false, which will show the login page.
|
||||
*/
|
||||
invalidateEmail() {
|
||||
this.validatedEmail = false;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<div id="login-with-device-page">
|
||||
<div id="content" class="content">
|
||||
<img class="logo-image" alt="Bitwarden" />
|
||||
|
||||
<ng-container *ngIf="state == StateEnum.StandardAuthRequest">
|
||||
<p class="lead text-center">{{ "logInRequestSent" | i18n }}</p>
|
||||
|
||||
<div class="box last">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<div class="section">
|
||||
<p class="section">
|
||||
{{ "notificationSentDevicePart1" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="primary"
|
||||
class="tw-cursor-pointer"
|
||||
[href]="deviceManagementUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ "notificationSentDeviceAnchor" | i18n }}</a
|
||||
>. {{ "notificationSentDevicePart2" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="fingerprint section">
|
||||
<h4>{{ "fingerprintPhraseHeader" | i18n }}</h4>
|
||||
<code>{{ fingerprintPhrase }}</code>
|
||||
</div>
|
||||
|
||||
<div class="section" *ngIf="showResendNotification">
|
||||
<a [routerLink]="[]" disabled="true" (click)="startAuthRequestLogin()">{{
|
||||
"resendNotification" | i18n
|
||||
}}</a>
|
||||
</div>
|
||||
|
||||
<div class="sub-options another-method">
|
||||
<p class="no-margin description-text">
|
||||
{{ "needAnotherOption" | i18n }}
|
||||
<a type="button" class="text text-primary" (click)="back()">
|
||||
{{ "viewAllLoginOptions" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="state == StateEnum.AdminAuthRequest">
|
||||
<p class="lead text-center">{{ "adminApprovalRequested" | i18n }}</p>
|
||||
|
||||
<div class="box last">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<div class="section">
|
||||
<p class="section">{{ "adminApprovalRequestSentToAdmins" | i18n }}</p>
|
||||
<p class="section">{{ "youWillBeNotifiedOnceApproved" | i18n }}</p>
|
||||
</div>
|
||||
|
||||
<div class="fingerprint section">
|
||||
<h4>{{ "fingerprintPhraseHeader" | i18n }}</h4>
|
||||
<code>{{ fingerprintPhrase }}</code>
|
||||
</div>
|
||||
|
||||
<div class="sub-options another-method">
|
||||
<p class="no-margin description-text">
|
||||
{{ "troubleLoggingIn" | i18n }}
|
||||
<a type="button" class="text text-primary" (click)="back()">
|
||||
{{ "viewAllLoginOptions" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #environment></ng-template>
|
||||
@@ -1,117 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Location } from "@angular/common";
|
||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { LoginViaAuthRequestComponentV1 as BaseLoginViaAuthRequestComponentV1 } from "@bitwarden/angular/auth/components/login-via-auth-request-v1.component";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import {
|
||||
AuthRequestServiceAbstraction,
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { EnvironmentComponent } from "../environment.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-login-via-auth-request",
|
||||
templateUrl: "login-via-auth-request-v1.component.html",
|
||||
})
|
||||
export class LoginViaAuthRequestComponentV1 extends BaseLoginViaAuthRequestComponentV1 {
|
||||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||
environmentModal: ViewContainerRef;
|
||||
showingModal = false;
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
keyService: KeyService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
appIdService: AppIdService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
apiService: ApiService,
|
||||
authService: AuthService,
|
||||
logService: LogService,
|
||||
environmentService: EnvironmentService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
anonymousHubService: AnonymousHubService,
|
||||
validationService: ValidationService,
|
||||
private modalService: ModalService,
|
||||
syncService: SyncService,
|
||||
loginEmailService: LoginEmailServiceAbstraction,
|
||||
deviceTrustService: DeviceTrustServiceAbstraction,
|
||||
authRequestService: AuthRequestServiceAbstraction,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
private location: Location,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
keyService,
|
||||
cryptoFunctionService,
|
||||
appIdService,
|
||||
passwordGenerationService,
|
||||
apiService,
|
||||
authService,
|
||||
logService,
|
||||
environmentService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
anonymousHubService,
|
||||
validationService,
|
||||
accountService,
|
||||
loginEmailService,
|
||||
deviceTrustService,
|
||||
authRequestService,
|
||||
loginStrategyService,
|
||||
toastService,
|
||||
);
|
||||
|
||||
this.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
||||
async settings() {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
EnvironmentComponent,
|
||||
this.environmentModal,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
modal.onShown.subscribe(() => {
|
||||
this.showingModal = true;
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
modal.onClosed.subscribe(() => {
|
||||
this.showingModal = false;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
childComponent.onSaved.subscribe(() => {
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
|
||||
back() {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,10 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components
|
||||
import { SharedModule } from "../../app/shared/shared.module";
|
||||
|
||||
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
|
||||
import { LoginComponentV1 } from "./login-v1.component";
|
||||
import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, RouterModule],
|
||||
declarations: [
|
||||
LoginComponentV1,
|
||||
LoginViaAuthRequestComponentV1,
|
||||
EnvironmentSelectorComponent,
|
||||
LoginDecryptionOptionsComponentV1,
|
||||
],
|
||||
exports: [LoginComponentV1, LoginViaAuthRequestComponentV1],
|
||||
declarations: [EnvironmentSelectorComponent, LoginDecryptionOptionsComponentV1],
|
||||
exports: [],
|
||||
})
|
||||
export class LoginModule {}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
@@ -40,6 +41,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
policyApiService: PolicyApiServiceAbstraction,
|
||||
policyService: PolicyService,
|
||||
router: Router,
|
||||
masterPasswordApiService: MasterPasswordApiService,
|
||||
syncService: SyncService,
|
||||
route: ActivatedRoute,
|
||||
private broadcasterService: BroadcasterService,
|
||||
@@ -63,6 +65,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
policyApiService,
|
||||
policyService,
|
||||
router,
|
||||
masterPasswordApiService,
|
||||
apiService,
|
||||
syncService,
|
||||
route,
|
||||
|
||||
@@ -25,16 +25,6 @@ export class MainSshAgentService {
|
||||
private logService: LogService,
|
||||
private messagingService: MessagingService,
|
||||
) {
|
||||
ipcMain.handle(
|
||||
"sshagent.importkey",
|
||||
async (
|
||||
event: any,
|
||||
{ privateKey, password }: { privateKey: string; password?: string },
|
||||
): Promise<sshagent.SshKeyImportResult> => {
|
||||
return sshagent.importKey(privateKey, password);
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("sshagent.init", async (event: any, message: any) => {
|
||||
this.init();
|
||||
});
|
||||
|
||||
@@ -24,8 +24,6 @@ import {
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
@@ -58,23 +56,13 @@ export class SshAgentService implements OnDestroy {
|
||||
private toastService: ToastService,
|
||||
private i18nService: I18nService,
|
||||
private desktopSettingsService: DesktopSettingsService,
|
||||
private configService: ConfigService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async init() {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.SSHAgent)
|
||||
.pipe(
|
||||
concatMap(async (enabled) => {
|
||||
this.isFeatureFlagEnabled = enabled;
|
||||
if (!(await ipc.platform.sshAgent.isLoaded()) && enabled) {
|
||||
await ipc.platform.sshAgent.init();
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
if (!(await ipc.platform.sshAgent.isLoaded())) {
|
||||
await ipc.platform.sshAgent.init();
|
||||
}
|
||||
|
||||
await this.initListeners();
|
||||
}
|
||||
|
||||
@@ -3060,9 +3060,6 @@
|
||||
"adminApprovalRequestSentToAdmins": {
|
||||
"message": "Your request has been sent to your admin."
|
||||
},
|
||||
"youWillBeNotifiedOnceApproved": {
|
||||
"message": "You will be notified once approved."
|
||||
},
|
||||
"troubleLoggingIn": {
|
||||
"message": "Trouble logging in?"
|
||||
},
|
||||
@@ -3532,9 +3529,6 @@
|
||||
"unknownApplication": {
|
||||
"message": "An application"
|
||||
},
|
||||
"sshKeyPasswordUnsupported": {
|
||||
"message": "Importing password protected SSH keys is not yet supported"
|
||||
},
|
||||
"invalidSshKey": {
|
||||
"message": "The SSH key is invalid"
|
||||
},
|
||||
@@ -3544,7 +3538,7 @@
|
||||
"importSshKeyFromClipboard": {
|
||||
"message": "Import key from clipboard"
|
||||
},
|
||||
"sshKeyPasted": {
|
||||
"sshKeyImported": {
|
||||
"message": "SSH key imported successfully"
|
||||
},
|
||||
"fileSavedToDevice": {
|
||||
|
||||
@@ -284,6 +284,8 @@ export class Main {
|
||||
this.migrationRunner.run().then(
|
||||
async () => {
|
||||
await this.toggleHardwareAcceleration();
|
||||
// Reset modal mode to make sure main window is displayed correctly
|
||||
await this.desktopSettingsService.resetInModalMode();
|
||||
await this.windowMain.init();
|
||||
await this.i18nService.init();
|
||||
await this.messagingMain.init();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import * as path from "path";
|
||||
import * as url from "url";
|
||||
|
||||
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
@@ -9,6 +10,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { BiometricStateService, BiometricsService } from "@bitwarden/key-management";
|
||||
|
||||
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
|
||||
import { cleanUserAgent, isDev } from "../utils";
|
||||
|
||||
import { WindowMain } from "./window.main";
|
||||
|
||||
@@ -49,6 +51,11 @@ export class TrayMain {
|
||||
label: this.i18nService.t("showHide"),
|
||||
click: () => this.toggleWindow(),
|
||||
},
|
||||
{
|
||||
visible: isDev(),
|
||||
label: "Fake Popup",
|
||||
click: () => this.fakePopup(),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: this.i18nService.t("exit"),
|
||||
@@ -190,7 +197,7 @@ export class TrayMain {
|
||||
this.hideDock();
|
||||
}
|
||||
} else {
|
||||
this.windowMain.win.show();
|
||||
this.windowMain.show();
|
||||
if (this.isDarwin()) {
|
||||
this.showDock();
|
||||
}
|
||||
@@ -203,4 +210,38 @@ export class TrayMain {
|
||||
this.windowMain.win.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to test modal behavior during development and could be removed in the future.
|
||||
* @returns
|
||||
*/
|
||||
private async fakePopup() {
|
||||
if (this.windowMain.win == null || this.windowMain.win.isDestroyed()) {
|
||||
await this.windowMain.createWindow("modal-app");
|
||||
return;
|
||||
}
|
||||
|
||||
// Restyle existing
|
||||
const existingWin = this.windowMain.win;
|
||||
|
||||
await this.desktopSettingsService.setInModalMode(true);
|
||||
await existingWin.loadURL(
|
||||
url.format({
|
||||
protocol: "file:",
|
||||
//pathname: `${__dirname}/index.html`,
|
||||
pathname: path.join(__dirname, "/index.html"),
|
||||
slashes: true,
|
||||
hash: "/passkeys",
|
||||
query: {
|
||||
redirectUrl: "/passkeys",
|
||||
},
|
||||
}),
|
||||
{
|
||||
userAgent: cleanUserAgent(existingWin.webContents.userAgent),
|
||||
},
|
||||
);
|
||||
existingWin.once("ready-to-show", () => {
|
||||
existingWin.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as path from "path";
|
||||
import * as url from "url";
|
||||
|
||||
import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "electron";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { concatMap, firstValueFrom, pairwise } from "rxjs";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
@@ -14,6 +14,7 @@ import { processisolations } from "@bitwarden/desktop-napi";
|
||||
import { BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
import { WindowState } from "../platform/models/domain/window-state";
|
||||
import { applyMainWindowStyles, applyPopupModalStyles } from "../platform/popup-modal-styles";
|
||||
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
|
||||
import { cleanUserAgent, isDev, isLinux, isMac, isMacAppStore, isWindows } from "../utils";
|
||||
|
||||
@@ -77,6 +78,24 @@ export class WindowMain {
|
||||
}
|
||||
});
|
||||
|
||||
this.desktopSettingsService.inModalMode$
|
||||
.pipe(
|
||||
pairwise(),
|
||||
concatMap(async ([lastValue, newValue]) => {
|
||||
if (lastValue && !newValue) {
|
||||
// Reset the window state to the main window state
|
||||
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
||||
// Because modal is used in front of another app, UX wise it makes sense to hide the main window when leaving modal mode.
|
||||
this.win.hide();
|
||||
} else if (!lastValue && newValue) {
|
||||
// Apply the popup modal styles
|
||||
applyPopupModalStyles(this.win);
|
||||
this.win.show();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.desktopSettingsService.preventScreenshots$.subscribe((prevent) => {
|
||||
if (this.win == null) {
|
||||
return;
|
||||
@@ -182,7 +201,20 @@ export class WindowMain {
|
||||
});
|
||||
}
|
||||
|
||||
async createWindow(): Promise<void> {
|
||||
/// Show the window with main window styles
|
||||
show() {
|
||||
if (this.win != null) {
|
||||
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
||||
this.win.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main window. The template argument is used to determine the styling of the window and what url will be loaded.
|
||||
* When the template is "modal-app", the window will be styled as a modal and the passkeys page will be loaded.
|
||||
* TODO: We might want to refactor the template argument to accomodate more target pages, e.g. ssh-agent.
|
||||
*/
|
||||
async createWindow(template: "full-app" | "modal-app" = "full-app"): Promise<void> {
|
||||
this.windowStates[mainWindowSizeKey] = await this.getWindowState(
|
||||
this.defaultWidth,
|
||||
this.defaultHeight,
|
||||
@@ -216,6 +248,12 @@ export class WindowMain {
|
||||
},
|
||||
});
|
||||
|
||||
if (template === "modal-app") {
|
||||
applyPopupModalStyles(this.win);
|
||||
} else {
|
||||
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
||||
}
|
||||
|
||||
this.win.webContents.on("dom-ready", () => {
|
||||
this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0;
|
||||
});
|
||||
@@ -225,21 +263,41 @@ export class WindowMain {
|
||||
}
|
||||
|
||||
// Show it later since it might need to be maximized.
|
||||
this.win.show();
|
||||
// use once event to avoid flash on unstyled content.
|
||||
this.win.once("ready-to-show", () => {
|
||||
this.win.show();
|
||||
});
|
||||
|
||||
// and load the index.html of the app.
|
||||
// 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.win.loadURL(
|
||||
url.format({
|
||||
protocol: "file:",
|
||||
pathname: path.join(__dirname, "/index.html"),
|
||||
slashes: true,
|
||||
}),
|
||||
{
|
||||
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
||||
},
|
||||
);
|
||||
if (template === "full-app") {
|
||||
// and load the index.html of the app.
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
void this.win.loadURL(
|
||||
url.format({
|
||||
protocol: "file:",
|
||||
pathname: path.join(__dirname, "/index.html"),
|
||||
slashes: true,
|
||||
}),
|
||||
{
|
||||
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// we're in modal mode - load the passkeys page
|
||||
await this.win.loadURL(
|
||||
url.format({
|
||||
protocol: "file:",
|
||||
pathname: path.join(__dirname, "/index.html"),
|
||||
slashes: true,
|
||||
hash: "/passkeys",
|
||||
query: {
|
||||
redirectUrl: "/passkeys",
|
||||
},
|
||||
}),
|
||||
{
|
||||
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Open the DevTools.
|
||||
if (isDev()) {
|
||||
@@ -336,6 +394,12 @@ export class WindowMain {
|
||||
return;
|
||||
}
|
||||
|
||||
const inModalMode = await firstValueFrom(this.desktopSettingsService.inModalMode$);
|
||||
|
||||
if (inModalMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const bounds = win.getBounds();
|
||||
|
||||
@@ -346,9 +410,14 @@ export class WindowMain {
|
||||
}
|
||||
}
|
||||
|
||||
this.windowStates[configKey].isMaximized = win.isMaximized();
|
||||
// We treat fullscreen as maximized (would be even better to store isFullscreen as its own flag).
|
||||
this.windowStates[configKey].isMaximized = win.isMaximized() || win.isFullScreen();
|
||||
this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds;
|
||||
|
||||
// Maybe store these as well?
|
||||
// win.isFocused();
|
||||
// win.isVisible();
|
||||
|
||||
if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) {
|
||||
this.windowStates[configKey].x = bounds.x;
|
||||
this.windowStates[configKey].y = bounds.y;
|
||||
|
||||
52
apps/desktop/src/platform/popup-modal-styles.ts
Normal file
52
apps/desktop/src/platform/popup-modal-styles.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
import { WindowState } from "./models/domain/window-state";
|
||||
|
||||
// change as needed, however limited by mainwindow minimum size
|
||||
const popupWidth = 680;
|
||||
const popupHeight = 500;
|
||||
|
||||
export function applyPopupModalStyles(window: BrowserWindow) {
|
||||
window.unmaximize();
|
||||
window.setSize(popupWidth, popupHeight);
|
||||
window.center();
|
||||
window.setWindowButtonVisibility?.(false);
|
||||
window.setMenuBarVisibility?.(false);
|
||||
window.setResizable(false);
|
||||
window.setAlwaysOnTop(true);
|
||||
|
||||
// Adjusting from full screen is a bit more hassle
|
||||
if (window.isFullScreen()) {
|
||||
window.setFullScreen(false);
|
||||
window.once("leave-full-screen", () => {
|
||||
window.setSize(popupWidth, popupHeight);
|
||||
window.center();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function applyMainWindowStyles(window: BrowserWindow, existingWindowState: WindowState) {
|
||||
window.setMinimumSize(680, 500);
|
||||
|
||||
// need to guard against null/undefined values
|
||||
|
||||
if (existingWindowState?.width && existingWindowState?.height) {
|
||||
window.setSize(existingWindowState.width, existingWindowState.height);
|
||||
}
|
||||
|
||||
if (existingWindowState?.x && existingWindowState?.y) {
|
||||
window.setPosition(existingWindowState.x, existingWindowState.y);
|
||||
}
|
||||
|
||||
window.setWindowButtonVisibility?.(true);
|
||||
window.setMenuBarVisibility?.(true);
|
||||
window.setResizable(true);
|
||||
window.setAlwaysOnTop(false);
|
||||
|
||||
// We're currently not recovering the maximized state, mostly due to conflicts with hiding the window.
|
||||
// window.setFullScreen(existingWindowState.isMaximized);
|
||||
|
||||
// if (existingWindowState.isMaximized) {
|
||||
// window.maximize();
|
||||
// }
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { sshagent as ssh } from "desktop_native/napi";
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
@@ -64,13 +63,6 @@ const sshAgent = {
|
||||
clearKeys: async () => {
|
||||
return await ipcRenderer.invoke("sshagent.clearkeys");
|
||||
},
|
||||
importKey: async (key: string, password: string): Promise<ssh.SshKeyImportResult> => {
|
||||
const res = await ipcRenderer.invoke("sshagent.importkey", {
|
||||
privateKey: key,
|
||||
password: password,
|
||||
});
|
||||
return res;
|
||||
},
|
||||
isLoaded(): Promise<boolean> {
|
||||
return ipcRenderer.invoke("sshagent.isloaded");
|
||||
},
|
||||
|
||||
@@ -75,6 +75,10 @@ const MINIMIZE_ON_COPY = new UserKeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "
|
||||
clearOn: [], // User setting, no need to clear
|
||||
});
|
||||
|
||||
const IN_MODAL_MODE = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "inModalMode", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
const PREVENT_SCREENSHOTS = new KeyDefinition<boolean>(
|
||||
DESKTOP_SETTINGS_DISK,
|
||||
"preventScreenshots",
|
||||
@@ -170,6 +174,10 @@ export class DesktopSettingsService {
|
||||
*/
|
||||
minimizeOnCopy$ = this.minimizeOnCopyState.state$.pipe(map(Boolean));
|
||||
|
||||
private readonly inModalModeState = this.stateProvider.getGlobal(IN_MODAL_MODE);
|
||||
|
||||
inModalMode$ = this.inModalModeState.state$.pipe(map(Boolean));
|
||||
|
||||
constructor(private stateProvider: StateProvider) {
|
||||
this.window$ = this.windowState.state$.pipe(
|
||||
map((window) =>
|
||||
@@ -178,6 +186,14 @@ export class DesktopSettingsService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to clear the setting on application start to make sure we don't end up
|
||||
* stuck in modal mode if the application is force-closed in modal mode.
|
||||
*/
|
||||
async resetInModalMode() {
|
||||
await this.inModalModeState.update(() => false);
|
||||
}
|
||||
|
||||
async setHardwareAcceleration(enabled: boolean) {
|
||||
await this.hwState.update(() => enabled);
|
||||
}
|
||||
@@ -286,6 +302,14 @@ export class DesktopSettingsService {
|
||||
await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the modal mode of the application. Setting this changes the windows-size and other properties.
|
||||
* @param value `true` if the application is in modal mode, `false` if it is not.
|
||||
*/
|
||||
async setInModalMode(value: boolean) {
|
||||
await this.inModalModeState.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the screenshot protection is enabled.
|
||||
* @param value `true` if the screenshot protection is enabled, `false` if it is not.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@import "variables.scss";
|
||||
|
||||
#login-page,
|
||||
#login-with-device-page,
|
||||
#lock-page,
|
||||
#sso-page,
|
||||
#set-password-page,
|
||||
@@ -191,7 +190,6 @@
|
||||
}
|
||||
|
||||
#login-page,
|
||||
#login-with-device-page,
|
||||
#login-decryption-options-page {
|
||||
flex-direction: column;
|
||||
justify-content: unset;
|
||||
@@ -222,41 +220,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#login-with-device-page {
|
||||
.content {
|
||||
display: block;
|
||||
padding-top: 70px;
|
||||
width: 350px !important;
|
||||
|
||||
.fingerprint {
|
||||
margin: auto;
|
||||
width: 315px;
|
||||
|
||||
.fingerpint-header {
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.another-method {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
.description-text {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
@include themify($themes) {
|
||||
color: themed("codeColor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#login-approval-page {
|
||||
.section-title {
|
||||
padding: 20px;
|
||||
|
||||
@@ -512,6 +512,15 @@
|
||||
[ngClass]="{ 'bwi-eye': !showPrivateKey, 'bwi-eye-slash': showPrivateKey }"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'importSshKeyFromClipboard' | i18n }}"
|
||||
(click)="importSshKeyFromClipboard()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-paste" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
@@ -559,16 +568,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
(click)="importSshKeyFromClipboard()"
|
||||
>
|
||||
{{ "importSshKeyFromClipboard" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { sshagent as sshAgent } from "desktop_native/napi";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component";
|
||||
@@ -25,8 +23,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { SshKeyPasswordPromptComponent } from "@bitwarden/importer-ui";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||
|
||||
const BroadcasterSubscriptionId = "AddEditComponent";
|
||||
|
||||
@@ -60,6 +57,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
toastService: ToastService,
|
||||
cipherAuthorizationService: CipherAuthorizationService,
|
||||
sdkService: SdkService,
|
||||
sshImportPromptService: SshImportPromptService,
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
@@ -82,6 +80,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
cipherAuthorizationService,
|
||||
toastService,
|
||||
sdkService,
|
||||
sshImportPromptService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,69 +158,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
this.cipher.revisionDate = cipher.revisionDate;
|
||||
}
|
||||
|
||||
async importSshKeyFromClipboard(password: string = "") {
|
||||
const key = await this.platformUtilsService.readFromClipboard();
|
||||
const parsedKey = await ipc.platform.sshAgent.importKey(key, password);
|
||||
if (parsedKey == null) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t("invalidSshKey"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (parsedKey.status) {
|
||||
case sshAgent.SshKeyImportStatus.ParsingError:
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t("invalidSshKey"),
|
||||
});
|
||||
return;
|
||||
case sshAgent.SshKeyImportStatus.UnsupportedKeyType:
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t("sshKeyTypeUnsupported"),
|
||||
});
|
||||
return;
|
||||
case sshAgent.SshKeyImportStatus.PasswordRequired:
|
||||
case sshAgent.SshKeyImportStatus.WrongPassword:
|
||||
if (password !== "") {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t("sshKeyWrongPassword"),
|
||||
});
|
||||
} else {
|
||||
password = await this.getSshKeyPassword();
|
||||
if (password === "") {
|
||||
return;
|
||||
}
|
||||
await this.importSshKeyFromClipboard(password);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey;
|
||||
this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey;
|
||||
this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("sshKeyPasted"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getSshKeyPassword(): Promise<string> {
|
||||
const dialog = this.dialogService.open<string>(SshKeyPasswordPromptComponent, {
|
||||
ariaModal: true,
|
||||
});
|
||||
|
||||
return await lastValueFrom(dialog.closed);
|
||||
}
|
||||
|
||||
truncateString(value: string, length: number) {
|
||||
return value.length > length ? value.substring(0, length) + "..." : value;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
<li
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SshKey }"
|
||||
*ngIf="isSshKeysEnabled"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/type-filter.component";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-type-filter",
|
||||
templateUrl: "type-filter.component.html",
|
||||
})
|
||||
export class TypeFilterComponent extends BaseTypeFilterComponent implements OnInit {
|
||||
isSshKeysEnabled = false;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
export class TypeFilterComponent extends BaseTypeFilterComponent {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user