mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
Autofill/pm 17444 use reprompt (#14004)
* Passkey stuff Co-authored-by: Anders Åberg <github@andersaberg.com> * Ugly hacks * Work On Modal State Management * Applying modalStyles * modal * Improved hide/show * fixed promise * File name * fix prettier * Protecting against null API's and undefined data * Only show fake popup to devs * cleanup mock code * rename minmimal-app to modal-app * Added comment * Added comment * removed old comment * Avoided changing minimum size * Add small comment * Rename component * adress feedback * Fixed uppercase file * Fixed build * Added codeowners * added void * commentary * feat: reset setting on app start * Moved reset to be in main / process launch * Add comment to create window * Added a little bit of styling * Use Messaging service to loadUrl * Enable passkeysautofill * Add logging * halfbaked * Integration working * And now it works without extra delay * Clean up * add note about messaging * lb * removed console.logs * Cleanup and adress review feedback * This hides the swift UI * add modal components * update modal with correct ciphers and functionality * add create screen * pick credential, draft * Remove logger * a whole lot of wiring * not working * Improved wiring * Cancel after 90s * Introduced observable * update cipher handling * update to use matchesUri * Launching bitwarden if its not running * Passing position from native to electron * Rename inModalMode to modalMode * remove tap * revert spaces * added back isDev * cleaned up a bit * Cleanup swift file * tweaked logging * clean up * Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Update apps/desktop/src/platform/services/desktop-settings.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * adress position feedback * Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Removed extra logging * Adjusted error logging * Use .error to log errors * remove dead code * Update desktop-autofill.service.ts * use parseCredentialId instead of guidToRawFormat * Update apps/desktop/src/autofill/services/desktop-autofill.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Change windowXy to a Record instead of [number,number] * Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Remove unsued dep and comment * changed timeout to be spec recommended maxium, 10 minutes, for now. * Correctly assume UP * Removed extra cancelRequest in deinint * Add timeout and UV to confirmChoseCipher UV is performed by UI, not the service * Improved docs regarding undefined cipherId * cleanup: UP is no longer undefined * Run completeError if ipc messages conversion failed * don't throw, instead return undefined * Disabled passkey provider * Throw error if no activeUserId was found * removed comment * Fixed lint * removed unsued service * reset entitlement formatting * Update entitlements.mas.plist * Fix build issues * Fix import issues * Update route names to use `fido2` * Fix being unable to select a passkey * Fix linting issues * Added support for handling a locked vault * Followup to fix merge issues and other comments * Update `userHandle` value * Add error handling for missing session or other errors * Remove unused route * Fix linting issues * Simplify updateCredential method * Add master password reprompt on passkey create * Followup to remove comments and timeouts and handle errors * Address lint issue by using `takeUntilDestroyed` * Add MP prompt to cipher selection * Change how timeout is handled * Include `of` from rxjs * Hide blue header for passkey popouts (#14095) * Hide blue header for passkey popouts * Fix issue with test * Fix ngOnDestroy complaint * Import OnDestroy correctly * Only require master password if item requires it --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Anders Åberg <github@andersaberg.com> Co-authored-by: Anders Åberg <anders@andersaberg.com> Co-authored-by: Colton Hurst <colton@coltonhurst.com> Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com> Co-authored-by: Evan Bassler <evanbassler@Mac.attlocal.net> Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
This commit is contained in:
@@ -86,7 +86,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
|||||||
<ng-template #exportVault></ng-template>
|
<ng-template #exportVault></ng-template>
|
||||||
<ng-template #appGenerator></ng-template>
|
<ng-template #appGenerator></ng-template>
|
||||||
<ng-template #loginApproval></ng-template>
|
<ng-template #loginApproval></ng-template>
|
||||||
<app-header></app-header>
|
<app-header *ngIf="showHeader$ | async"></app-header>
|
||||||
|
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div class="loading" *ngIf="loading">
|
<div class="loading" *ngIf="loading">
|
||||||
@@ -112,6 +112,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild("loginApproval", { read: ViewContainerRef, static: true })
|
@ViewChild("loginApproval", { read: ViewContainerRef, static: true })
|
||||||
loginApprovalModalRef: ViewContainerRef;
|
loginApprovalModalRef: ViewContainerRef;
|
||||||
|
|
||||||
|
showHeader$ = this.accountService.showHeader$;
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
private lastActivity: Date = null;
|
private lastActivity: Date = null;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
|||||||
|
|
||||||
private updatedCipher: CipherView;
|
private updatedCipher: CipherView;
|
||||||
|
|
||||||
private rpId = new BehaviorSubject<string>("");
|
private rpId = new BehaviorSubject<string>(null);
|
||||||
private availableCipherIdsSubject = new BehaviorSubject<string[]>([""]);
|
private availableCipherIdsSubject = new BehaviorSubject<string[]>([""]);
|
||||||
/**
|
/**
|
||||||
* Observable that emits available cipher IDs once they're confirmed by the UI
|
* Observable that emits available cipher IDs once they're confirmed by the UI
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
disableMargin
|
disableMargin
|
||||||
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
|
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
|
||||||
>
|
>
|
||||||
<bit-section-header class="tw-bg-background">
|
<bit-section-header class="passkey-header tw-bg-background">
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="tw-flex tw-items-center">
|
||||||
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
|
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-close"
|
bitIconButton="bwi-close"
|
||||||
slot="end"
|
slot="end"
|
||||||
class="tw-mb-4 tw-mr-2"
|
class="passkey-header-close tw-mb-4 tw-mr-2"
|
||||||
(click)="closeModal()"
|
(click)="closeModal()"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit, OnDestroy } from "@angular/core";
|
||||||
import { RouterModule, Router } from "@angular/router";
|
import { RouterModule, Router } from "@angular/router";
|
||||||
import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs";
|
import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs";
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
SectionHeaderComponent,
|
SectionHeaderComponent,
|
||||||
BitIconButtonComponent,
|
BitIconButtonComponent,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DesktopFido2UserInterfaceService,
|
DesktopFido2UserInterfaceService,
|
||||||
@@ -47,7 +48,7 @@ import { DesktopSettingsService } from "../../../platform/services/desktop-setti
|
|||||||
],
|
],
|
||||||
templateUrl: "fido2-create.component.html",
|
templateUrl: "fido2-create.component.html",
|
||||||
})
|
})
|
||||||
export class Fido2CreateComponent implements OnInit {
|
export class Fido2CreateComponent implements OnInit, OnDestroy {
|
||||||
session?: DesktopFido2UserInterfaceSession = null;
|
session?: DesktopFido2UserInterfaceSession = null;
|
||||||
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||||
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
||||||
@@ -61,10 +62,12 @@ export class Fido2CreateComponent implements OnInit {
|
|||||||
private readonly dialogService: DialogService,
|
private readonly dialogService: DialogService,
|
||||||
private readonly domainSettingsService: DomainSettingsService,
|
private readonly domainSettingsService: DomainSettingsService,
|
||||||
private readonly logService: LogService,
|
private readonly logService: LogService,
|
||||||
|
private readonly passwordRepromptService: PasswordRepromptService,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
await this.accountService.setShowHeader(false);
|
||||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||||
const rpid = await this.session.getRpId();
|
const rpid = await this.session.getRpId();
|
||||||
const equivalentDomains = await firstValueFrom(
|
const equivalentDomains = await firstValueFrom(
|
||||||
@@ -92,8 +95,16 @@ export class Fido2CreateComponent implements OnInit {
|
|||||||
.catch((error) => this.logService.error(error));
|
.catch((error) => this.logService.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ngOnDestroy() {
|
||||||
|
await this.accountService.setShowHeader(true);
|
||||||
|
}
|
||||||
|
|
||||||
async addPasskeyToCipher(cipher: CipherView) {
|
async addPasskeyToCipher(cipher: CipherView) {
|
||||||
this.session.notifyConfirmCreateCredential(true, cipher);
|
const userVerified = cipher.reprompt
|
||||||
|
? await this.passwordRepromptService.showPasswordPrompt()
|
||||||
|
: true;
|
||||||
|
|
||||||
|
this.session.notifyConfirmCreateCredential(userVerified, cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmPasskey() {
|
async confirmPasskey() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
disableMargin
|
disableMargin
|
||||||
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
|
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
|
||||||
>
|
>
|
||||||
<bit-section-header class="tw-bg-background">
|
<bit-section-header class="passkey-header tw-bg-background">
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="tw-flex tw-items-center">
|
||||||
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
|
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-close"
|
bitIconButton="bwi-close"
|
||||||
slot="end"
|
slot="end"
|
||||||
class="tw-mb-4 tw-mr-2"
|
class="passkey-header-close tw-mb-4 tw-mr-2"
|
||||||
(click)="closeModal()"
|
(click)="closeModal()"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<bit-section class="tw-bg-background-alt tw-p-4 tw-flex tw-flex-col tw-grow">
|
<bit-section class="tw-bg-background-alt tw-p-4 tw-flex tw-flex-col tw-grow">
|
||||||
<bit-item *ngFor="let c of ciphers$ | async" class="">
|
<bit-item *ngFor="let c of ciphers$ | async" class="">
|
||||||
<button type="button" bit-item-content (click)="chooseCipher(c.id)">
|
<button type="button" bit-item-content (click)="chooseCipher(c)">
|
||||||
<app-vault-icon [cipher]="c" slot="start"></app-vault-icon>
|
<app-vault-icon [cipher]="c" slot="start"></app-vault-icon>
|
||||||
<button bitLink [title]="c.name" type="button">
|
<button bitLink [title]="c.name" type="button">
|
||||||
{{ c.name }}
|
{{ c.name }}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, OnInit, OnDestroy } from "@angular/core";
|
import { Component, OnInit, OnDestroy } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
||||||
import { RouterModule, Router } from "@angular/router";
|
import { RouterModule, Router } from "@angular/router";
|
||||||
import { firstValueFrom, map, BehaviorSubject, Observable } from "rxjs";
|
import { firstValueFrom, map, BehaviorSubject, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { BitwardenShield } from "@bitwarden/auth/angular";
|
import { BitwardenShield } from "@bitwarden/auth/angular";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import {
|
import {
|
||||||
BadgeModule,
|
BadgeModule,
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
BitIconButtonComponent,
|
BitIconButtonComponent,
|
||||||
SectionHeaderComponent,
|
SectionHeaderComponent,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DesktopFido2UserInterfaceService,
|
DesktopFido2UserInterfaceService,
|
||||||
@@ -48,6 +49,7 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings
|
|||||||
})
|
})
|
||||||
export class Fido2VaultComponent implements OnInit, OnDestroy {
|
export class Fido2VaultComponent implements OnInit, OnDestroy {
|
||||||
session?: DesktopFido2UserInterfaceSession = null;
|
session?: DesktopFido2UserInterfaceSession = null;
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||||
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
||||||
private cipherIdsSubject = new BehaviorSubject<string[]>([]);
|
private cipherIdsSubject = new BehaviorSubject<string[]>([]);
|
||||||
@@ -60,10 +62,12 @@ export class Fido2VaultComponent implements OnInit, OnDestroy {
|
|||||||
private readonly cipherService: CipherService,
|
private readonly cipherService: CipherService,
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
private readonly logService: LogService,
|
private readonly logService: LogService,
|
||||||
|
private readonly passwordRepromptService: PasswordRepromptService,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
await this.accountService.setShowHeader(false);
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
);
|
);
|
||||||
@@ -71,22 +75,30 @@ export class Fido2VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||||
this.cipherIds$ = this.session?.availableCipherIds$;
|
this.cipherIds$ = this.session?.availableCipherIds$;
|
||||||
|
|
||||||
this.cipherIds$.pipe(takeUntilDestroyed()).subscribe((cipherIds) => {
|
this.cipherIds$.pipe(takeUntil(this.destroy$)).subscribe((cipherIds) => {
|
||||||
this.cipherService
|
this.cipherService
|
||||||
.getAllDecrypted(activeUserId)
|
.getAllDecryptedForIds(activeUserId, cipherIds || [])
|
||||||
.then((ciphers) => {
|
.then((ciphers) => {
|
||||||
this.ciphersSubject.next(ciphers.filter((cipher) => cipherIds.includes(cipher.id)));
|
this.ciphersSubject.next(ciphers);
|
||||||
})
|
})
|
||||||
.catch((error) => this.logService.error(error));
|
.catch((error) => this.logService.error(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
|
await this.accountService.setShowHeader(true);
|
||||||
this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject
|
this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject
|
||||||
}
|
}
|
||||||
|
|
||||||
async chooseCipher(cipherId: string) {
|
async chooseCipher(cipher: CipherView) {
|
||||||
this.session?.confirmChosenCipher(cipherId, true);
|
if (
|
||||||
|
cipher.reprompt !== CipherRepromptType.None &&
|
||||||
|
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||||
|
) {
|
||||||
|
this.session?.confirmChosenCipher(cipher.id, false);
|
||||||
|
} else {
|
||||||
|
this.session?.confirmChosenCipher(cipher.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
await this.router.navigate(["/"]);
|
await this.router.navigate(["/"]);
|
||||||
await this.desktopSettingsService.setModalMode(false);
|
await this.desktopSettingsService.setModalMode(false);
|
||||||
|
|||||||
@@ -232,3 +232,11 @@
|
|||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.passkey-header {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.passkey-header-close {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ export class FakeAccountService implements AccountService {
|
|||||||
accountActivitySubject = new ReplaySubject<Record<UserId, Date>>(1);
|
accountActivitySubject = new ReplaySubject<Record<UserId, Date>>(1);
|
||||||
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
|
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
|
||||||
accountVerifyDevicesSubject = new ReplaySubject<boolean>(1);
|
accountVerifyDevicesSubject = new ReplaySubject<boolean>(1);
|
||||||
|
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
|
||||||
|
showHeaderSubject = new ReplaySubject<boolean>(1);
|
||||||
private _activeUserId: UserId;
|
private _activeUserId: UserId;
|
||||||
get activeUserId() {
|
get activeUserId() {
|
||||||
return this._activeUserId;
|
return this._activeUserId;
|
||||||
@@ -55,6 +57,7 @@ export class FakeAccountService implements AccountService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
showHeader$ = this.showHeaderSubject.asObservable();
|
||||||
get nextUpAccount$(): Observable<Account> {
|
get nextUpAccount$(): Observable<Account> {
|
||||||
return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe(
|
return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe(
|
||||||
map(([accounts, activeAccount, sortedUserIds]) => {
|
map(([accounts, activeAccount, sortedUserIds]) => {
|
||||||
@@ -114,6 +117,10 @@ export class FakeAccountService implements AccountService {
|
|||||||
this.accountsSubject.next(updated);
|
this.accountsSubject.next(updated);
|
||||||
await this.mock.clean(userId);
|
await this.mock.clean(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setShowHeader(value: boolean): Promise<void> {
|
||||||
|
this.showHeaderSubject.next(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loggedOutInfo: AccountInfo = {
|
const loggedOutInfo: AccountInfo = {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export abstract class AccountService {
|
|||||||
sortedUserIds$: Observable<UserId[]>;
|
sortedUserIds$: Observable<UserId[]>;
|
||||||
/** Next account that is not the current active account */
|
/** Next account that is not the current active account */
|
||||||
nextUpAccount$: Observable<Account>;
|
nextUpAccount$: Observable<Account>;
|
||||||
|
showHeader$: Observable<boolean>;
|
||||||
/**
|
/**
|
||||||
* Updates the `accounts$` observable with the new account data.
|
* Updates the `accounts$` observable with the new account data.
|
||||||
*
|
*
|
||||||
@@ -102,6 +103,11 @@ export abstract class AccountService {
|
|||||||
* @param lastActivity
|
* @param lastActivity
|
||||||
*/
|
*/
|
||||||
abstract setAccountActivity(userId: UserId, lastActivity: Date): Promise<void>;
|
abstract setAccountActivity(userId: UserId, lastActivity: Date): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Show the account switcher.
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
abstract setShowHeader(visible: boolean): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class InternalAccountService extends AccountService {
|
export abstract class InternalAccountService extends AccountService {
|
||||||
|
|||||||
@@ -429,6 +429,16 @@ describe("accountService", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("setShowHeader", () => {
|
||||||
|
it("should update _showHeader$ when setShowHeader is called", async () => {
|
||||||
|
expect(sut["_showHeader$"].value).toBe(true);
|
||||||
|
|
||||||
|
await sut.setShowHeader(false);
|
||||||
|
|
||||||
|
expect(sut["_showHeader$"].value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
|
BehaviorSubject,
|
||||||
Observable,
|
Observable,
|
||||||
switchMap,
|
switchMap,
|
||||||
filter,
|
filter,
|
||||||
@@ -84,6 +85,7 @@ export const getOptionalUserId = map<Account | null, UserId | null>(
|
|||||||
export class AccountServiceImplementation implements InternalAccountService {
|
export class AccountServiceImplementation implements InternalAccountService {
|
||||||
private accountsState: GlobalState<Record<UserId, AccountInfo>>;
|
private accountsState: GlobalState<Record<UserId, AccountInfo>>;
|
||||||
private activeAccountIdState: GlobalState<UserId | undefined>;
|
private activeAccountIdState: GlobalState<UserId | undefined>;
|
||||||
|
private _showHeader$ = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
accounts$: Observable<Record<UserId, AccountInfo>>;
|
accounts$: Observable<Record<UserId, AccountInfo>>;
|
||||||
activeAccount$: Observable<Account | null>;
|
activeAccount$: Observable<Account | null>;
|
||||||
@@ -91,6 +93,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||||||
accountVerifyNewDeviceLogin$: Observable<boolean>;
|
accountVerifyNewDeviceLogin$: Observable<boolean>;
|
||||||
sortedUserIds$: Observable<UserId[]>;
|
sortedUserIds$: Observable<UserId[]>;
|
||||||
nextUpAccount$: Observable<Account>;
|
nextUpAccount$: Observable<Account>;
|
||||||
|
showHeader$ = this._showHeader$.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
@@ -260,6 +263,10 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setShowHeader(visible: boolean): Promise<void> {
|
||||||
|
this._showHeader$.next(visible);
|
||||||
|
}
|
||||||
|
|
||||||
private async setAccountInfo(userId: UserId, update: Partial<AccountInfo>): Promise<void> {
|
private async setAccountInfo(userId: UserId, update: Partial<AccountInfo>): Promise<void> {
|
||||||
function newAccountInfo(oldAccountInfo: AccountInfo): AccountInfo {
|
function newAccountInfo(oldAccountInfo: AccountInfo): AccountInfo {
|
||||||
return { ...oldAccountInfo, ...update };
|
return { ...oldAccountInfo, ...update };
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch?: UriMatchStrategySetting,
|
defaultMatch?: UriMatchStrategySetting,
|
||||||
): Promise<CipherView[]>;
|
): Promise<CipherView[]>;
|
||||||
|
getAllDecryptedForIds: (userId: UserId, ids: string[]) => Promise<CipherView[]>;
|
||||||
abstract filterCiphersForUrl(
|
abstract filterCiphersForUrl(
|
||||||
ciphers: CipherView[],
|
ciphers: CipherView[],
|
||||||
url: string,
|
url: string,
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
|
|
||||||
async getAllDecryptedForUrl(
|
async getAllDecryptedForUrl(
|
||||||
url: string,
|
url: string,
|
||||||
userId: UserId,
|
userId?: UserId,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch: UriMatchStrategySetting = null,
|
defaultMatch: UriMatchStrategySetting = null,
|
||||||
): Promise<CipherView[]> {
|
): Promise<CipherView[]> {
|
||||||
@@ -521,6 +521,13 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
|
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAllDecryptedForIds(userId: UserId, ids: string[]): Promise<CipherView[]> {
|
||||||
|
if (userId) {
|
||||||
|
const ciphers = await this.getAllDecrypted(userId);
|
||||||
|
return ciphers.filter((cipher) => ids.includes(cipher.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async filterCiphersForUrl(
|
async filterCiphersForUrl(
|
||||||
ciphers: CipherView[],
|
ciphers: CipherView[],
|
||||||
url: string,
|
url: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user