1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

Merge branch 'main' into auth/pm-8111/browser-refresh-login-component

This commit is contained in:
Alec Rippberger
2024-10-03 21:35:15 -05:00
committed by GitHub
23 changed files with 134 additions and 87 deletions

View File

@@ -559,9 +559,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
} }
async fingerprint() { async fingerprint() {
const fingerprint = await this.cryptoService.getFingerprint( const activeUserId = await firstValueFrom(
await this.stateService.getUserId(), this.accountService.activeAccount$.pipe(map((a) => a?.id)),
); );
const publicKey = await firstValueFrom(this.cryptoService.userPublicKey$(activeUserId));
const fingerprint = await this.cryptoService.getFingerprint(activeUserId, publicKey);
const dialogRef = FingerprintDialogComponent.open(this.dialogService, { const dialogRef = FingerprintDialogComponent.open(this.dialogService, {
fingerprint, fingerprint,

View File

@@ -74,7 +74,7 @@ describe("AutofillInit", () => {
Object.defineProperty(document, "readyState", { value: "complete", writable: true }); Object.defineProperty(document, "readyState", { value: "complete", writable: true });
autofillInit.init(); autofillInit.init();
jest.advanceTimersByTime(250); jest.advanceTimersByTime(750);
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", { expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", {
sender: "autofillInit", sender: "autofillInit",

View File

@@ -78,7 +78,7 @@ class AutofillInit implements AutofillInitInterface {
this.clearCollectPageDetailsOnLoadTimeout(); this.clearCollectPageDetailsOnLoadTimeout();
this.collectPageDetailsOnLoadTimeout = setTimeout( this.collectPageDetailsOnLoadTimeout = setTimeout(
() => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }), () => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }),
250, 750,
); );
}; };

View File

@@ -15,7 +15,9 @@
class="bwi bwi-collection text-muted" class="bwi bwi-collection text-muted"
></i> ></i>
</span> </span>
<span class="detail" *ngIf="getSubName(cipher)">{{ getSubName(cipher) }}</span> <ng-container slot="secondary">
<span *ngIf="cipher.subTitle" slot="secondary">{{ cipher.subTitle }}</span> <div *ngIf="getSubName(cipher)">{{ getSubName(cipher) }}</div>
<div *ngIf="cipher.subTitle">{{ cipher.subTitle }}</div>
</ng-container>
</button> </button>
</bit-item> </bit-item>

View File

@@ -287,7 +287,7 @@ export class Fido2Component implements OnInit, OnDestroy {
const confirmed = await this.dialogService.openSimpleDialog({ const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "overwritePasskey" }, title: { key: "overwritePasskey" },
content: { key: "overwritePasskeyAlert" }, content: { key: "overwritePasskeyAlert" },
type: "info", type: "warning",
}); });
if (!confirmed) { if (!confirmed) {

View File

@@ -34,28 +34,30 @@ export class AutoFillConstants {
"totpcode", "totpcode",
"2facode", "2facode",
"approvals_code", "approvals_code",
"code",
"mfacode", "mfacode",
"otc",
"otc-code", "otc-code",
"otp", "onetimecode",
"otp-code", "otp-code",
"otpcode", "otpcode",
"pin", "onetimepassword",
"security_code", "security_code",
"twofactor", "twofactor",
"twofa", "twofa",
"twofactorcode", "twofactorcode",
"verificationCode", "verificationCode",
"verification code",
]; ];
static readonly AmbiguousTotpFieldNames: string[] = ["code", "pin", "otc", "otp"];
static readonly SearchFieldNames: string[] = ["search", "query", "find", "go"]; static readonly SearchFieldNames: string[] = ["search", "query", "find", "go"];
static readonly FieldIgnoreList: string[] = ["captcha", "findanything", "forgot"]; static readonly FieldIgnoreList: string[] = ["captcha", "findanything", "forgot"];
static readonly PasswordFieldExcludeList: string[] = [ static readonly PasswordFieldExcludeList: string[] = [
"hint",
...AutoFillConstants.FieldIgnoreList, ...AutoFillConstants.FieldIgnoreList,
"onetimepassword", ...AutoFillConstants.TotpFieldNames,
]; ];
static readonly ExcludedAutofillLoginTypes: string[] = [ static readonly ExcludedAutofillLoginTypes: string[] = [

View File

@@ -2260,29 +2260,23 @@ describe("AutofillService", () => {
options, options,
); );
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledTimes(4); expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith(
1,
usernameField, usernameField,
AutoFillConstants.UsernameFieldNames, AutoFillConstants.UsernameFieldNames,
); );
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith( expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
2,
emailField, emailField,
AutoFillConstants.UsernameFieldNames, AutoFillConstants.UsernameFieldNames,
); );
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith( expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
3,
telephoneField, telephoneField,
AutoFillConstants.UsernameFieldNames, AutoFillConstants.UsernameFieldNames,
); );
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith( expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
4,
totpField, totpField,
AutoFillConstants.UsernameFieldNames, AutoFillConstants.UsernameFieldNames,
); );
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenNthCalledWith( expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalledWith(
5,
nonViewableField, nonViewableField,
AutoFillConstants.UsernameFieldNames, AutoFillConstants.UsernameFieldNames,
); );
@@ -2328,6 +2322,7 @@ describe("AutofillService", () => {
it("will not attempt to fuzzy match a totp field if totp autofill is not allowed", async () => { it("will not attempt to fuzzy match a totp field if totp autofill is not allowed", async () => {
options.allowTotpAutofill = false; options.allowTotpAutofill = false;
jest.spyOn(autofillService as any, "findMatchingFieldIndex");
await autofillService["generateLoginFillScript"]( await autofillService["generateLoginFillScript"](
fillScript, fillScript,
@@ -2336,7 +2331,7 @@ describe("AutofillService", () => {
options, options,
); );
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalledWith( expect(autofillService["findMatchingFieldIndex"]).not.toHaveBeenCalledWith(
expect.anything(), expect.anything(),
AutoFillConstants.TotpFieldNames, AutoFillConstants.TotpFieldNames,
); );
@@ -2386,7 +2381,6 @@ describe("AutofillService", () => {
false, false,
false, false,
); );
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalled();
expect(AutofillService.fillByOpid).toHaveBeenCalledTimes(2); expect(AutofillService.fillByOpid).toHaveBeenCalledTimes(2);
expect(AutofillService.fillByOpid).toHaveBeenNthCalledWith( expect(AutofillService.fillByOpid).toHaveBeenNthCalledWith(
1, 1,

View File

@@ -887,7 +887,10 @@ export default class AutofillService implements AutofillServiceInterface {
options.allowTotpAutofill && options.allowTotpAutofill &&
f.viewable && f.viewable &&
(f.type === "text" || f.type === "number") && (f.type === "text" || f.type === "number") &&
(AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames) || (AutofillService.fieldIsFuzzyMatch(f, [
...AutoFillConstants.TotpFieldNames,
...AutoFillConstants.AmbiguousTotpFieldNames,
]) ||
f.autoCompleteType === "one-time-code") f.autoCompleteType === "one-time-code")
) { ) {
totps.push(f); totps.push(f);
@@ -2558,6 +2561,11 @@ export default class AutofillService implements AutofillServiceInterface {
return; return;
} }
// We want to avoid treating TOTP fields as password fields
if (AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames)) {
return;
}
const isLikePassword = () => { const isLikePassword = () => {
if (f.type !== "text") { if (f.type !== "text") {
return false; return false;
@@ -2670,12 +2678,18 @@ export default class AutofillService implements AutofillServiceInterface {
(withoutForm || f.form === passwordField.form) && (withoutForm || f.form === passwordField.form) &&
(canBeHidden || f.viewable) && (canBeHidden || f.viewable) &&
(f.type === "text" || f.type === "number") && (f.type === "text" || f.type === "number") &&
AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames) AutofillService.fieldIsFuzzyMatch(f, [
...AutoFillConstants.TotpFieldNames,
...AutoFillConstants.AmbiguousTotpFieldNames,
])
) { ) {
totpField = f; totpField = f;
if ( if (
this.findMatchingFieldIndex(f, AutoFillConstants.TotpFieldNames) > -1 || this.findMatchingFieldIndex(f, [
...AutoFillConstants.TotpFieldNames,
...AutoFillConstants.AmbiguousTotpFieldNames,
]) > -1 ||
f.autoCompleteType === "one-time-code" f.autoCompleteType === "one-time-code"
) { ) {
// We found an exact match. No need to keep looking. // We found an exact match. No need to keep looking.

View File

@@ -30,7 +30,6 @@ export class InlineMenuFieldQualificationService
this.webAuthnAutocompleteValue, this.webAuthnAutocompleteValue,
]); ]);
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(","); private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(",");
private currentPasswordAutocompleteValue = "current-password"; private currentPasswordAutocompleteValue = "current-password";
private newPasswordAutoCompleteValue = "new-password"; private newPasswordAutoCompleteValue = "new-password";
private autofillFieldKeywordsMap: AutofillKeywordsMap = new WeakMap(); private autofillFieldKeywordsMap: AutofillKeywordsMap = new WeakMap();
@@ -927,7 +926,7 @@ export class InlineMenuFieldQualificationService
return false; return false;
} }
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1); return !AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1);
} }
/** /**
@@ -1094,13 +1093,29 @@ export class InlineMenuFieldQualificationService
]; ];
const keywordsSet = new Set<string>(); const keywordsSet = new Set<string>();
for (let i = 0; i < keywords.length; i++) { for (let i = 0; i < keywords.length; i++) {
if (typeof keywords[i] === "string") { if (keywords[i] && typeof keywords[i] === "string") {
keywords[i] let keywordEl = keywords[i].toLowerCase();
.toLowerCase() keywordsSet.add(keywordEl);
.replace(/-/g, "")
.replace(/[^a-zA-Z0-9]+/g, "|") // Remove hyphens from all potential keywords, we want to treat these as a single word.
.split("|") keywordEl = keywordEl.replace(/-/g, "");
.forEach((keyword) => keywordsSet.add(keyword));
// Split the keyword by non-alphanumeric characters to get the keywords without treating a space as a separator.
keywordEl.split(/[^\p{L}\d]+/gu).forEach((keyword) => {
if (keyword) {
keywordsSet.add(keyword);
}
});
// Collapse all spaces and split by non-alphanumeric characters to get the keywords
keywordEl
.replace(/\s/g, "")
.split(/[^\p{L}\d]+/gu)
.forEach((keyword) => {
if (keyword) {
keywordsSet.add(keyword);
}
});
} }
} }

View File

@@ -551,7 +551,11 @@ export class GetCommand extends DownloadCommand {
private async getFingerprint(id: string) { private async getFingerprint(id: string) {
let fingerprint: string[] = null; let fingerprint: string[] = null;
if (id === "me") { if (id === "me") {
fingerprint = await this.cryptoService.getFingerprint(await this.stateService.getUserId()); const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const publicKey = await firstValueFrom(this.cryptoService.userPublicKey$(activeUserId));
fingerprint = await this.cryptoService.getFingerprint(activeUserId, publicKey);
} else if (Utils.isGuid(id)) { } else if (Utils.isGuid(id)) {
try { try {
const response = await this.apiService.getUserPublicKey(id); const response = await this.apiService.getUserPublicKey(id);

View File

@@ -2,7 +2,7 @@ import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import * as jsdom from "jsdom"; import * as jsdom from "jsdom";
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { import {
OrganizationUserApiService, OrganizationUserApiService,
@@ -119,7 +119,6 @@ import {
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service";
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
@@ -788,13 +787,13 @@ export class ServiceContainer {
this.authService.logOut(() => { this.authService.logOut(() => {
/* Do nothing */ /* Do nothing */
}); });
const userId = (await this.stateService.getUserId()) as UserId; const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
await Promise.all([ await Promise.all([
this.eventUploadService.uploadEvents(userId as UserId), this.eventUploadService.uploadEvents(userId),
this.cryptoService.clearKeys(), this.cryptoService.clearKeys(),
this.cipherService.clear(userId), this.cipherService.clear(userId),
this.folderService.clear(userId), this.folderService.clear(userId),
this.collectionService.clear(userId as UserId), this.collectionService.clear(userId),
]); ]);
await this.stateEventRunnerService.handleEvent("logout", userId); await this.stateEventRunnerService.handleEvent("logout", userId);

View File

@@ -196,22 +196,23 @@ export class SettingsComponent implements OnInit, OnDestroy {
async ngOnInit() { async ngOnInit() {
this.vaultTimeoutOptions = await this.generateVaultTimeoutOptions(); this.vaultTimeoutOptions = await this.generateVaultTimeoutOptions();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
this.userHasMasterPassword = await this.userVerificationService.hasMasterPassword();
this.isWindows = (await this.platformUtilsService.getDevice()) === DeviceType.WindowsDesktop;
this.isLinux = (await this.platformUtilsService.getDevice()) === DeviceType.LinuxDesktop; this.isLinux = (await this.platformUtilsService.getDevice()) === DeviceType.LinuxDesktop;
if ((await this.stateService.getUserId()) == null) { if (activeAccount == null || activeAccount.id == null) {
return; return;
} }
this.currentUserEmail = await firstValueFrom( this.userHasMasterPassword = await this.userVerificationService.hasMasterPassword();
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
); this.isWindows = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop;
this.currentUserId = (await this.stateService.getUserId()) as UserId;
this.currentUserEmail = activeAccount.email;
this.currentUserId = activeAccount.id;
this.availableVaultTimeoutActions$ = this.refreshTimeoutSettings$.pipe( this.availableVaultTimeoutActions$ = this.refreshTimeoutSettings$.pipe(
switchMap(() => this.vaultTimeoutSettingsService.availableVaultTimeoutActions$()), switchMap(() =>
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(activeAccount.id),
),
); );
// Load timeout policy // Load timeout policy
@@ -236,12 +237,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
}), }),
); );
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
// Load initial values // Load initial values
this.userHasPinSet = await this.pinService.isPinSet(userId); this.userHasPinSet = await this.pinService.isPinSet(activeAccount.id);
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
const initialValues = { const initialValues = {
vaultTimeout: await firstValueFrom( vaultTimeout: await firstValueFrom(

View File

@@ -235,7 +235,8 @@ export class AppComponent implements OnInit, OnDestroy {
this.modalService.closeAll(); this.modalService.closeAll();
if ( if (
message.userId == null || message.userId == null ||
message.userId === (await this.stateService.getUserId()) message.userId ===
(await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))))
) { ) {
await this.router.navigate(["lock"]); await this.router.navigate(["lock"]);
} }
@@ -274,9 +275,11 @@ export class AppComponent implements OnInit, OnDestroy {
await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef); await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef);
break; break;
case "showFingerprintPhrase": { case "showFingerprintPhrase": {
const fingerprint = await this.cryptoService.getFingerprint( const activeUserId = await firstValueFrom(
await this.stateService.getUserId(), this.accountService.activeAccount$.pipe(map((a) => a?.id)),
); );
const publicKey = await firstValueFrom(this.cryptoService.userPublicKey$(activeUserId));
const fingerprint = await this.cryptoService.getFingerprint(activeUserId, publicKey);
const dialogRef = FingerprintDialogComponent.open(this.dialogService, { fingerprint }); const dialogRef = FingerprintDialogComponent.open(this.dialogService, { fingerprint });
await firstValueFrom(dialogRef.closed); await firstValueFrom(dialogRef.closed);
break; break;

View File

@@ -201,8 +201,11 @@ export class AccountSwitcherComponent implements OnInit {
}): Promise<{ [userId: string]: InactiveAccount }> { }): Promise<{ [userId: string]: InactiveAccount }> {
const inactiveAccounts: { [userId: string]: InactiveAccount } = {}; const inactiveAccounts: { [userId: string]: InactiveAccount } = {};
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
for (const userId in baseAccounts) { for (const userId in baseAccounts) {
if (userId == null || userId === (await this.stateService.getUserId())) { if (userId == null || userId === activeUserId) {
continue; continue;
} }

View File

@@ -461,11 +461,10 @@ describe("LockComponent", () => {
}); });
describe("canUseBiometric", () => { describe("canUseBiometric", () => {
it("should call getUserId() on stateService", async () => { it("should call biometric.enabled with current active user", async () => {
stateServiceMock.getUserId.mockResolvedValue("userId");
await component["canUseBiometric"](); await component["canUseBiometric"]();
expect(ipc.keyManagement.biometric.enabled).toHaveBeenCalledWith("userId"); expect(ipc.keyManagement.biometric.enabled).toHaveBeenCalledWith(mockUserId);
}); });
}); });

View File

@@ -1,6 +1,6 @@
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, switchMap } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common";
@@ -182,7 +182,7 @@ export class LockComponent extends BaseLockComponent implements OnInit, OnDestro
} }
private async canUseBiometric() { private async canUseBiometric() {
const userId = await this.stateService.getUserId(); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
return await ipc.keyManagement.biometric.enabled(userId); return await ipc.keyManagement.biometric.enabled(userId);
} }

View File

@@ -23,7 +23,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { StateEventRunnerService } from "@bitwarden/common/platform/state";
import { SyncService } from "@bitwarden/common/platform/sync"; import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
@@ -277,7 +276,7 @@ export class AppComponent implements OnDestroy, OnInit {
await this.displayLogoutReason(logoutReason); await this.displayLogoutReason(logoutReason);
await this.eventUploadService.uploadEvents(); await this.eventUploadService.uploadEvents();
const userId = (await this.stateService.getUserId()) as UserId; const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const logoutPromise = firstValueFrom( const logoutPromise = firstValueFrom(
this.authService.authStatusFor$(userId).pipe( this.authService.authStatusFor$(userId).pipe(

View File

@@ -1,13 +1,12 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from "@angular/forms";
import { Subject, takeUntil } from "rxjs"; import { firstValueFrom, map, Subject, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component";
@@ -30,8 +29,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private accountService: AccountService,
private stateService: StateService,
private dialogService: DialogService, private dialogService: DialogService,
private toastService: ToastService, private toastService: ToastService,
) {} ) {}
@@ -39,7 +37,9 @@ export class ProfileComponent implements OnInit, OnDestroy {
async ngOnInit() { async ngOnInit() {
this.profile = await this.apiService.getProfile(); this.profile = await this.apiService.getProfile();
this.loading = false; this.loading = false;
this.fingerprintMaterial = await this.stateService.getUserId(); this.fingerprintMaterial = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.formGroup.get("name").setValue(this.profile.name); this.formGroup.get("name").setValue(this.profile.name);
this.formGroup.get("email").setValue(this.profile.email); this.formGroup.get("email").setValue(this.profile.email);

View File

@@ -1,8 +1,9 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { ApiKeyComponent } from "./api-key.component"; import { ApiKeyComponent } from "./api-key.component";
@@ -21,7 +22,7 @@ export class SecurityKeysComponent implements OnInit {
constructor( constructor(
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private stateService: StateService, private accountService: AccountService,
private apiService: ApiService, private apiService: ApiService,
private dialogService: DialogService, private dialogService: DialogService,
) {} ) {}
@@ -31,7 +32,9 @@ export class SecurityKeysComponent implements OnInit {
} }
async viewUserApiKey() { async viewUserApiKey() {
const entityId = await this.stateService.getUserId(); const entityId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
await ApiKeyComponent.open(this.dialogService, { await ApiKeyComponent.open(this.dialogService, {
data: { data: {
keyType: "user", keyType: "user",
@@ -47,7 +50,9 @@ export class SecurityKeysComponent implements OnInit {
} }
async rotateUserApiKey() { async rotateUserApiKey() {
const entityId = await this.stateService.getUserId(); const entityId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
await ApiKeyComponent.open(this.dialogService, { await ApiKeyComponent.open(this.dialogService, {
data: { data: {
keyType: "user", keyType: "user",

View File

@@ -58,6 +58,14 @@ export abstract class StateService<T extends Account = Account> {
setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>; setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise<string>; getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise<string>;
setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>; setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Use `TokenService.hasAccessToken$()` or `AuthService.authStatusFor$` instead.
*/
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>; getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
/**
* @deprecated Use `AccountService.activeAccount$` instead.
*/
getUserId: (options?: StorageOptions) => Promise<string>; getUserId: (options?: StorageOptions) => Promise<string>;
} }

View File

@@ -138,8 +138,6 @@ describe("VaultTimeoutService", () => {
return new BehaviorSubject<VaultTimeout>(accounts[userId]?.vaultTimeout); return new BehaviorSubject<VaultTimeout>(accounts[userId]?.vaultTimeout);
}); });
stateService.getUserId.mockResolvedValue(globalSetups?.userId);
// Set desired user active and known users on accounts service : note the only thing that matters here is that the ID are set // Set desired user active and known users on accounts service : note the only thing that matters here is that the ID are set
if (globalSetups?.userId) { if (globalSetups?.userId) {
accountService.activeAccountSubject.next({ accountService.activeAccountSubject.next({

13
package-lock.json generated
View File

@@ -178,7 +178,7 @@
"tsconfig-paths-webpack-plugin": "4.1.0", "tsconfig-paths-webpack-plugin": "4.1.0",
"type-fest": "2.19.0", "type-fest": "2.19.0",
"typescript": "5.1.6", "typescript": "5.1.6",
"url": "0.11.3", "url": "0.11.4",
"util": "0.12.5", "util": "0.12.5",
"wait-on": "8.0.1", "wait-on": "8.0.1",
"webpack": "5.94.0", "webpack": "5.94.0",
@@ -37794,14 +37794,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/url": { "node_modules/url": {
"version": "0.11.3", "version": "0.11.4",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
"integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"punycode": "^1.4.1", "punycode": "^1.4.1",
"qs": "^6.11.2" "qs": "^6.12.3"
},
"engines": {
"node": ">= 0.4"
} }
}, },
"node_modules/url-parse": { "node_modules/url-parse": {

View File

@@ -139,7 +139,7 @@
"tsconfig-paths-webpack-plugin": "4.1.0", "tsconfig-paths-webpack-plugin": "4.1.0",
"type-fest": "2.19.0", "type-fest": "2.19.0",
"typescript": "5.1.6", "typescript": "5.1.6",
"url": "0.11.3", "url": "0.11.4",
"util": "0.12.5", "util": "0.12.5",
"wait-on": "8.0.1", "wait-on": "8.0.1",
"webpack": "5.94.0", "webpack": "5.94.0",