1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-03 17:13:47 +00:00

feat(accounts): Add creationDate of account to AccountInfo

* Add creationDate of account to AccountInfo

* Added initialization of creationDate.

* Removed extra changes.

* Fixed tests to initialize creation date

* Added helper method to abstract account initialization in tests.

* More test updates.

* Linting

* Additional test fixes.

* Fixed spec reference

* Fixed imports

* Linting.

* Fixed browser test.

* Modified tsconfig to reference spec file.

* Fixed import.

* Removed dependency on os.  This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node.

* Revert "Removed dependency on os.  This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node."

This reverts commit 669f6557b6.

* Updated stories to hard-code new field.

* Removed changes to tsconfig

* Revert "Removed changes to tsconfig"

This reverts commit b7d916e8dc.
This commit is contained in:
Todd Martin
2025-12-12 10:03:31 -05:00
committed by GitHub
parent be9d0c0291
commit 27d82aaf28
60 changed files with 491 additions and 276 deletions

View File

@@ -2,14 +2,11 @@ import { Observable } from "rxjs";
import { UserId } from "../../types/guid";
/**
* Holds information about an account for use in the AccountService
* if more information is added, be sure to update the equality method.
*/
export type AccountInfo = {
email: string;
emailVerified: boolean;
name: string | undefined;
creationDate: string | undefined;
};
export type Account = { id: UserId } & AccountInfo;
@@ -75,6 +72,12 @@ export abstract class AccountService {
* @param emailVerified
*/
abstract setAccountEmailVerified(userId: UserId, emailVerified: boolean): Promise<void>;
/**
* updates the `accounts$` observable with the creation date for the account.
* @param userId
* @param creationDate
*/
abstract setAccountCreationDate(userId: UserId, creationDate: string): Promise<void>;
/**
* updates the `accounts$` observable with the new VerifyNewDeviceLogin property for the account.
* @param userId

View File

@@ -6,6 +6,7 @@
import { MockProxy, mock } from "jest-mock-extended";
import { firstValueFrom } from "rxjs";
import { mockAccountInfoWith } from "../../../spec/fake-account-service";
import { FakeGlobalState } from "../../../spec/fake-state";
import {
FakeGlobalStateProvider,
@@ -27,7 +28,7 @@ import {
} from "./account.service";
describe("accountInfoEqual", () => {
const accountInfo: AccountInfo = { name: "name", email: "email", emailVerified: true };
const accountInfo = mockAccountInfoWith();
it("compares nulls", () => {
expect(accountInfoEqual(null, null)).toBe(true);
@@ -64,6 +65,23 @@ describe("accountInfoEqual", () => {
expect(accountInfoEqual(accountInfo, same)).toBe(true);
expect(accountInfoEqual(accountInfo, different)).toBe(false);
});
it("compares creationDate", () => {
const same = { ...accountInfo };
const different = { ...accountInfo, creationDate: "2024-12-31T00:00:00.000Z" };
expect(accountInfoEqual(accountInfo, same)).toBe(true);
expect(accountInfoEqual(accountInfo, different)).toBe(false);
});
it("compares undefined creationDate", () => {
const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined });
const same = { ...accountWithoutCreationDate };
const different = { ...accountWithoutCreationDate, creationDate: "2024-01-01T00:00:00.000Z" };
expect(accountInfoEqual(accountWithoutCreationDate, same)).toBe(true);
expect(accountInfoEqual(accountWithoutCreationDate, different)).toBe(false);
});
});
describe("accountService", () => {
@@ -76,7 +94,10 @@ describe("accountService", () => {
let activeAccountIdState: FakeGlobalState<UserId>;
let accountActivityState: FakeGlobalState<Record<UserId, Date>>;
const userId = Utils.newGuid() as UserId;
const userInfo = { email: "email", name: "name", emailVerified: true };
const userInfo = mockAccountInfoWith({
email: "email",
name: "name",
});
beforeEach(() => {
messagingService = mock();
@@ -253,6 +274,56 @@ describe("accountService", () => {
});
});
describe("setCreationDate", () => {
const initialState = { [userId]: userInfo };
beforeEach(() => {
accountsState.stateSubject.next(initialState);
});
it("should update the account with a new creation date", async () => {
const newCreationDate = "2024-12-31T00:00:00.000Z";
await sut.setAccountCreationDate(userId, newCreationDate);
const currentState = await firstValueFrom(accountsState.state$);
expect(currentState).toEqual({
[userId]: { ...userInfo, creationDate: newCreationDate },
});
});
it("should not update if the creation date is the same", async () => {
await sut.setAccountCreationDate(userId, userInfo.creationDate);
const currentState = await firstValueFrom(accountsState.state$);
expect(currentState).toEqual(initialState);
});
it("should update from undefined to a defined creation date", async () => {
const accountWithoutCreationDate = mockAccountInfoWith({
...userInfo,
creationDate: undefined,
});
accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate });
const newCreationDate = "2024-06-15T12:30:00.000Z";
await sut.setAccountCreationDate(userId, newCreationDate);
const currentState = await firstValueFrom(accountsState.state$);
expect(currentState).toEqual({
[userId]: { ...accountWithoutCreationDate, creationDate: newCreationDate },
});
});
it("should update to a different creation date string format", async () => {
const newCreationDate = "2023-03-15T08:45:30.123Z";
await sut.setAccountCreationDate(userId, newCreationDate);
const currentState = await firstValueFrom(accountsState.state$);
expect(currentState).toEqual({
[userId]: { ...userInfo, creationDate: newCreationDate },
});
});
});
describe("setAccountVerifyNewDeviceLogin", () => {
const initialState = true;
beforeEach(() => {
@@ -294,6 +365,7 @@ describe("accountService", () => {
email: "",
emailVerified: false,
name: undefined,
creationDate: undefined,
},
});
});

View File

@@ -62,6 +62,7 @@ const LOGGED_OUT_INFO: AccountInfo = {
email: "",
emailVerified: false,
name: undefined,
creationDate: undefined,
};
/**
@@ -167,6 +168,10 @@ export class AccountServiceImplementation implements InternalAccountService {
await this.setAccountInfo(userId, { emailVerified });
}
async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> {
await this.setAccountInfo(userId, { creationDate });
}
async clean(userId: UserId) {
await this.setAccountInfo(userId, LOGGED_OUT_INFO);
await this.removeAccountActivity(userId);

View File

@@ -15,6 +15,7 @@ import {
SystemNotificationEvent,
SystemNotificationsService,
} from "@bitwarden/common/platform/system-notifications/system-notifications.service";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/user-core";
import { AuthRequestAnsweringService } from "./auth-request-answering.service";
@@ -48,14 +49,16 @@ describe("AuthRequestAnsweringService", () => {
// Common defaults
authService.activeAccountStatus$ = of(AuthenticationStatus.Locked);
accountService.activeAccount$ = of({
id: userId,
const accountInfo = mockAccountInfoWith({
email: "user@example.com",
emailVerified: true,
name: "User",
});
accountService.activeAccount$ = of({
id: userId,
...accountInfo,
});
accountService.accounts$ = of({
[userId]: { email: "user@example.com", emailVerified: true, name: "User" },
[userId]: accountInfo,
});
(masterPasswordService.forceSetPasswordReason$ as jest.Mock).mockReturnValue(
of(ForceSetPasswordReason.None),

View File

@@ -10,6 +10,7 @@ import {
makeStaticByteArray,
mockAccountServiceWith,
trackEmissions,
mockAccountInfoWith,
} from "../../../spec";
import { ApiService } from "../../abstractions/api.service";
import { MessagingService } from "../../platform/abstractions/messaging.service";
@@ -58,9 +59,10 @@ describe("AuthService", () => {
const accountInfo = {
status: AuthenticationStatus.Unlocked,
id: userId,
email: "email",
emailVerified: false,
name: "name",
...mockAccountInfoWith({
email: "email",
name: "name",
}),
};
beforeEach(() => {
@@ -112,9 +114,10 @@ describe("AuthService", () => {
const accountInfo2 = {
status: AuthenticationStatus.Unlocked,
id: Utils.newGuid() as UserId,
email: "email2",
emailVerified: false,
name: "name2",
...mockAccountInfoWith({
email: "email2",
name: "name2",
}),
};
const emissions = trackEmissions(sut.activeAccountStatus$);
@@ -131,11 +134,13 @@ describe("AuthService", () => {
it("requests auth status for all known users", async () => {
const userId2 = Utils.newGuid() as UserId;
await accountService.addAccount(userId2, {
email: "email2",
emailVerified: false,
name: "name2",
});
await accountService.addAccount(
userId2,
mockAccountInfoWith({
email: "email2",
name: "name2",
}),
);
const mockFn = jest.fn().mockReturnValue(of(AuthenticationStatus.Locked));
sut.authStatusFor$ = mockFn;

View File

@@ -8,12 +8,13 @@ import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "@bitwarden/key-management";
import { mockAccountInfoWith } from "../../../spec/fake-account-service";
import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { UserId } from "../../types/guid";
import { Account, AccountInfo, AccountService } from "../abstractions/account.service";
import { Account, AccountService } from "../abstractions/account.service";
import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation";
@@ -96,11 +97,10 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
const encryptedKey = { encryptedString: "encryptedString" };
organizationApiService.getKeys.mockResolvedValue(orgKeyResponse as any);
const user1AccountInfo: AccountInfo = {
const user1AccountInfo = mockAccountInfoWith({
name: "Test User 1",
email: "test1@email.com",
emailVerified: true,
};
});
activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId }));
keyService.userKey$.mockReturnValue(of({ key: "key" } as any));