Skip to content

Commit

Permalink
[PM-14219] Add service for new device verification notice (#11988)
Browse files Browse the repository at this point in the history
* added service and spec file for new device verification notice
  • Loading branch information
Jingo88 authored Nov 19, 2024
1 parent 140a514 commit 2185559
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions libs/common/src/platform/state/state-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,7 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro
});
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk");
export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition(
"newDeviceVerificationNotice",
"disk",
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { firstValueFrom } from "rxjs";

import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";

import {
FakeAccountService,
FakeSingleUserState,
FakeStateProvider,
mockAccountServiceWith,
} from "../../../common/spec";

import {
NewDeviceVerificationNoticeService,
NewDeviceVerificationNotice,
NEW_DEVICE_VERIFICATION_NOTICE_KEY,
} from "./new-device-verification-notice.service";

describe("New Device Verification Notice", () => {
const sut = NEW_DEVICE_VERIFICATION_NOTICE_KEY;
const userId = Utils.newGuid() as UserId;
let newDeviceVerificationService: NewDeviceVerificationNoticeService;
let mockNoticeState: FakeSingleUserState<NewDeviceVerificationNotice>;
let stateProvider: FakeStateProvider;
let accountService: FakeAccountService;

beforeEach(() => {
accountService = mockAccountServiceWith(userId);
stateProvider = new FakeStateProvider(accountService);
mockNoticeState = stateProvider.singleUser.getFake(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
newDeviceVerificationService = new NewDeviceVerificationNoticeService(stateProvider);
});

it("should deserialize newDeviceVerificationNotice values", async () => {
const currentDate = new Date();
const inputObj = {
last_dismissal: currentDate,
permanent_dismissal: false,
};

const expectedFolderData = {
last_dismissal: currentDate.toJSON(),
permanent_dismissal: false,
};

const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj)));

expect(result).toEqual(expectedFolderData);
});

describe("notice$", () => {
it("emits new device verification notice state", async () => {
const currentDate = new Date();
const data = {
last_dismissal: currentDate,
permanent_dismissal: false,
};
await stateProvider.setUserState(NEW_DEVICE_VERIFICATION_NOTICE_KEY, data, userId);

const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));

expect(result).toBe(data);
});
});

describe("update notice state", () => {
it("should update the date with a new value", async () => {
const currentDate = new Date();
const oldDate = new Date("11-11-2011");
const oldState = {
last_dismissal: oldDate,
permanent_dismissal: false,
};
const newState = {
last_dismissal: currentDate,
permanent_dismissal: true,
};
mockNoticeState.nextState(oldState);
await newDeviceVerificationService.updateNewDeviceVerificationNoticeState(userId, newState);

const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));
expect(result).toEqual(newState);
});
});
});
65 changes: 65 additions & 0 deletions libs/vault/src/services/new-device-verification-notice.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Jsonify } from "type-fest";

import {
StateProvider,
UserKeyDefinition,
NEW_DEVICE_VERIFICATION_NOTICE,
SingleUserState,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";

// This service checks when to show New Device Verification Notice to Users
// It will be a two phase approach and the values below will work with two different feature flags
// If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting
// permanent_dismissal will be checked if the user should never see the notice again
export class NewDeviceVerificationNotice {
last_dismissal: Date;
permanent_dismissal: boolean;

constructor(obj: Partial<NewDeviceVerificationNotice>) {
if (obj == null) {
return;
}
this.last_dismissal = obj.last_dismissal || null;
this.permanent_dismissal = obj.permanent_dismissal || null;
}

static fromJSON(obj: Jsonify<NewDeviceVerificationNotice>) {
return Object.assign(new NewDeviceVerificationNotice({}), obj);
}
}

export const NEW_DEVICE_VERIFICATION_NOTICE_KEY =
new UserKeyDefinition<NewDeviceVerificationNotice>(
NEW_DEVICE_VERIFICATION_NOTICE,
"noticeState",
{
deserializer: (obj: Jsonify<NewDeviceVerificationNotice>) =>
NewDeviceVerificationNotice.fromJSON(obj),
clearOn: [],
},
);

@Injectable()
export class NewDeviceVerificationNoticeService {
constructor(private stateProvider: StateProvider) {}

private noticeState(userId: UserId): SingleUserState<NewDeviceVerificationNotice> {
return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
}

noticeState$(userId: UserId): Observable<NewDeviceVerificationNotice> {
return this.noticeState(userId).state$;
}

async updateNewDeviceVerificationNoticeState(
userId: UserId,
newState: NewDeviceVerificationNotice,
): Promise<void> {
await this.noticeState(userId).update(() => {
return { ...newState };
});
}
}

0 comments on commit 2185559

Please sign in to comment.