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:
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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[] = [
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
13
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user