mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +00:00
feat(auth): [PM-8221] implement device verification for unknown devices
Add device verification flow that requires users to enter an OTP when logging in from an unrecognized device. This includes: - New device verification route and guard - Email OTP verification component - Authentication timeout handling PM-8221
This commit is contained in:
71
libs/angular/src/auth/guards/active-auth.guard.spec.ts
Normal file
71
libs/angular/src/auth/guards/active-auth.guard.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { Router } from "@angular/router";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
import { activeAuthGuard } from "./active-auth.guard";
|
||||
|
||||
@Component({ template: "" })
|
||||
class EmptyComponent {}
|
||||
|
||||
describe("activeAuthGuard", () => {
|
||||
const setup = (authType: AuthenticationType | null) => {
|
||||
const loginStrategyService: MockProxy<LoginStrategyServiceAbstraction> =
|
||||
mock<LoginStrategyServiceAbstraction>();
|
||||
const currentAuthTypeSubject = new BehaviorSubject<AuthenticationType | null>(authType);
|
||||
loginStrategyService.currentAuthType$ = currentAuthTypeSubject;
|
||||
|
||||
const logService: MockProxy<LogService> = mock<LogService>();
|
||||
|
||||
const testBed = TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: "", component: EmptyComponent },
|
||||
{
|
||||
path: "protected-route",
|
||||
component: EmptyComponent,
|
||||
canActivate: [activeAuthGuard()],
|
||||
},
|
||||
{ path: "login", component: EmptyComponent },
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
{ provide: LoginStrategyServiceAbstraction, useValue: loginStrategyService },
|
||||
{ provide: LogService, useValue: logService },
|
||||
],
|
||||
declarations: [EmptyComponent],
|
||||
});
|
||||
|
||||
return {
|
||||
router: testBed.inject(Router),
|
||||
logService,
|
||||
loginStrategyService,
|
||||
};
|
||||
};
|
||||
|
||||
it("creates the guard", () => {
|
||||
const { router } = setup(AuthenticationType.Password);
|
||||
expect(router).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows access with an active login session", async () => {
|
||||
const { router } = setup(AuthenticationType.Password);
|
||||
|
||||
await router.navigate(["protected-route"]);
|
||||
expect(router.url).toBe("/protected-route");
|
||||
});
|
||||
|
||||
it("redirects to login with no active session", async () => {
|
||||
const { router, logService } = setup(null);
|
||||
|
||||
await router.navigate(["protected-route"]);
|
||||
expect(router.url).toBe("/login");
|
||||
expect(logService.error).toHaveBeenCalledWith("No active login session found.");
|
||||
});
|
||||
});
|
||||
28
libs/angular/src/auth/guards/active-auth.guard.ts
Normal file
28
libs/angular/src/auth/guards/active-auth.guard.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
/**
|
||||
* Guard that ensures there is an active login session before allowing access
|
||||
* to the new device verification route.
|
||||
* If not, redirects to login.
|
||||
*/
|
||||
export function activeAuthGuard(): CanActivateFn {
|
||||
return async () => {
|
||||
const loginStrategyService = inject(LoginStrategyServiceAbstraction);
|
||||
const logService = inject(LogService);
|
||||
const router = inject(Router);
|
||||
|
||||
// Check if we have a valid login session
|
||||
const authType = await firstValueFrom(loginStrategyService.currentAuthType$);
|
||||
if (authType === null) {
|
||||
logService.error("No active login session found.");
|
||||
return router.createUrlTree(["/login"]);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./auth.guard";
|
||||
export * from "./active-auth.guard";
|
||||
export * from "./lock.guard";
|
||||
export * from "./redirect.guard";
|
||||
export * from "./tde-decryption-required.guard";
|
||||
|
||||
Reference in New Issue
Block a user