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

[PM-14347][PM-14348] New Device Verification Logic (#12451)

* add account created date to the account information

* set permanent dismissal flag when the user selects that they can access their email

* update the logic of device verification notice

* add service to cache the profile creation date to avoid calling the API multiple times

* update step one logic for new device verification + add tests

* update step two logic for new device verification + add tests
- remove remind me later link for permanent logic

* migrate 2FA check to use the profile property rather than hitting the API directly.

The API for 2FA providers is only available on web so it didn't work for browser & native.

* remove unneeded account related changes

- profile creation is used from other sources

* remove obsolete test

* store the profile id within the vault service

* remove unused map

* store the associated profile id so account for profile switching in the extension

* add comment for temporary service and ticket number to remove

* formatting

* move up logic for feature flags
This commit is contained in:
Nick Krantz
2024-12-19 09:55:39 -06:00
committed by GitHub
parent 0f3803ac91
commit e129e90faa
9 changed files with 865 additions and 22 deletions

View File

@@ -0,0 +1,94 @@
import { TestBed } from "@angular/core/testing";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultProfileService } from "./vault-profile.service";
describe("VaultProfileService", () => {
let service: VaultProfileService;
const userId = "profile-id";
const hardcodedDateString = "2024-02-24T12:00:00Z";
const getProfile = jest.fn().mockResolvedValue({
creationDate: hardcodedDateString,
twoFactorEnabled: true,
id: "new-user-id",
});
beforeEach(() => {
getProfile.mockClear();
TestBed.configureTestingModule({
providers: [{ provide: ApiService, useValue: { getProfile } }],
});
jest.useFakeTimers();
jest.setSystemTime(new Date("2024-02-22T00:00:00Z"));
service = TestBed.runInInjectionContext(() => new VaultProfileService());
service["userId"] = userId;
});
afterEach(() => {
jest.useRealTimers();
});
describe("getProfileCreationDate", () => {
it("calls `getProfile` when stored profile date is not set", async () => {
expect(service["profileCreatedDate"]).toBeNull();
const date = await service.getProfileCreationDate(userId);
expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z");
expect(getProfile).toHaveBeenCalled();
});
it("calls `getProfile` when stored profile id does not match", async () => {
service["profileCreatedDate"] = hardcodedDateString;
service["userId"] = "old-user-id";
const date = await service.getProfileCreationDate(userId);
expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z");
expect(getProfile).toHaveBeenCalled();
});
it("does not call `getProfile` when the date is already stored", async () => {
service["profileCreatedDate"] = hardcodedDateString;
const date = await service.getProfileCreationDate(userId);
expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z");
expect(getProfile).not.toHaveBeenCalled();
});
});
describe("getProfileTwoFactorEnabled", () => {
it("calls `getProfile` when stored 2FA property is not stored", async () => {
expect(service["profile2FAEnabled"]).toBeNull();
const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId);
expect(twoFactorEnabled).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("calls `getProfile` when stored profile id does not match", async () => {
service["profile2FAEnabled"] = false;
service["userId"] = "old-user-id";
const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId);
expect(twoFactorEnabled).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("does not call `getProfile` when 2FA property is already stored", async () => {
service["profile2FAEnabled"] = false;
const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId);
expect(twoFactorEnabled).toBe(false);
expect(getProfile).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,64 @@
import { Injectable, inject } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
@Injectable({
providedIn: "root",
})
/**
* Class to provide profile level details without having to call the API each time.
* NOTE: This is a temporary service and can be replaced once the `UnauthenticatedExtensionUIRefresh` flag goes live.
* The `UnauthenticatedExtensionUIRefresh` introduces a sync that takes place upon logging in. These details can then
* be added to account object and retrieved from there.
* TODO: PM-16202
*/
export class VaultProfileService {
private apiService = inject(ApiService);
private userId: string | null = null;
/** Profile creation stored as a string. */
private profileCreatedDate: string | null = null;
/** True when 2FA is enabled on the profile. */
private profile2FAEnabled: boolean | null = null;
/**
* Returns the creation date of the profile.
* Note: `Date`s are mutable in JS, creating a new
* instance is important to avoid unwanted changes.
*/
async getProfileCreationDate(userId: string): Promise<Date> {
if (this.profileCreatedDate && userId === this.userId) {
return Promise.resolve(new Date(this.profileCreatedDate));
}
const profile = await this.fetchAndCacheProfile();
return new Date(profile.creationDate);
}
/**
* Returns whether there is a 2FA provider on the profile.
*/
async getProfileTwoFactorEnabled(userId: string): Promise<boolean> {
if (this.profile2FAEnabled !== null && userId === this.userId) {
return Promise.resolve(this.profile2FAEnabled);
}
const profile = await this.fetchAndCacheProfile();
return profile.twoFactorEnabled;
}
private async fetchAndCacheProfile(): Promise<ProfileResponse> {
const profile = await this.apiService.getProfile();
this.userId = profile.id;
this.profileCreatedDate = profile.creationDate;
this.profile2FAEnabled = profile.twoFactorEnabled;
return profile;
}
}