1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

Refactored fido2 popup to use auth guard when routing to component, added BrowserRouterService to track previous page and route using that

This commit is contained in:
gbubemismith
2023-09-04 15:29:48 -04:00
parent 43db7f6d60
commit c204c70e6b
6 changed files with 123 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
import { inject } from "@angular/core";
import {
ActivatedRouteSnapshot,
CanActivateFn,
Router,
RouterStateSnapshot,
} from "@angular/router";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { BrowserRouterService } from "../../platform/popup/services/browser-router.service";
export const fido2AuthGuard: CanActivateFn = async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) => {
const routerService = inject(BrowserRouterService);
const authService = inject(AuthService);
const router = inject(Router);
const authStatus = await authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
await routerService.setPreviousUrl(state.url);
return router.createUrlTree(["/home"], { queryParams: route.queryParams });
}
if (authStatus === AuthenticationStatus.Locked) {
await routerService.setPreviousUrl(state.url);
return router.createUrlTree(["/lock"], { queryParams: route.queryParams });
}
return true;
};

View File

@@ -0,0 +1,50 @@
import { Injectable } from "@angular/core";
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from "@angular/router";
import { filter } from "rxjs";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@Injectable({
providedIn: "root",
})
export class BrowserRouterService {
constructor(router: Router, private stateService: StateService) {
router.events
.pipe(filter((e) => e instanceof NavigationEnd))
.subscribe((event: NavigationEnd) => {
const state: ActivatedRouteSnapshot = router.routerState.snapshot.root;
let child = state.firstChild;
while (child.firstChild) {
child = child.firstChild;
}
const updateUrl = !child?.data?.doNotSaveUrl ?? true;
if (updateUrl) {
this.setPreviousUrl(event.url);
}
});
}
async getPreviousUrl() {
return this.stateService.getPreviousUrl();
}
// Check validity of previous url
async hasPreviousUrl() {
return (await this.getPreviousUrl()) != "/";
}
async setPreviousUrl(url: string) {
await this.stateService.setPreviousUrl(url);
}
private getDeepestChild(activatedRoute: ActivatedRoute): ActivatedRoute {
let child = activatedRoute;
while (child.firstChild) {
child = child.firstChild;
}
return child;
}
}

View File

@@ -11,6 +11,7 @@ import {
import { canAccessFeature } from "@bitwarden/angular/guard/feature-flag.guard"; import { canAccessFeature } from "@bitwarden/angular/guard/feature-flag.guard";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard";
import { EnvironmentComponent } from "../auth/popup/environment.component"; import { EnvironmentComponent } from "../auth/popup/environment.component";
import { HintComponent } from "../auth/popup/hint.component"; import { HintComponent } from "../auth/popup/hint.component";
import { HomeComponent } from "../auth/popup/home.component"; import { HomeComponent } from "../auth/popup/home.component";
@@ -72,17 +73,19 @@ const routes: Routes = [
path: "home", path: "home",
component: HomeComponent, component: HomeComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuard],
data: { state: "home" }, data: { state: "home", doNotSaveUrl: true },
}, },
{ {
path: "fido2", path: "fido2",
component: Fido2Component, component: Fido2Component,
canActivate: [fido2AuthGuard],
data: { state: "fido2" },
}, },
{ {
path: "login", path: "login",
component: LoginComponent, component: LoginComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuard],
data: { state: "login" }, data: { state: "login", doNotSaveUrl: true },
}, },
{ {
path: "login-with-device", path: "login-with-device",
@@ -100,13 +103,13 @@ const routes: Routes = [
path: "lock", path: "lock",
component: LockComponent, component: LockComponent,
canActivate: [lockGuard()], canActivate: [lockGuard()],
data: { state: "lock" }, data: { state: "lock", doNotSaveUrl: true },
}, },
{ {
path: "2fa", path: "2fa",
component: TwoFactorComponent, component: TwoFactorComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuard],
data: { state: "2fa" }, data: { state: "2fa", doNotSaveUrl: true },
}, },
{ {
path: "2fa-options", path: "2fa-options",

View File

@@ -524,4 +524,17 @@ export abstract class StateService<T extends Account = Account> {
value: Record<string, Record<string, boolean>>, value: Record<string, Record<string, boolean>>,
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;
/**
* fetches string value of the URL stored here, usually only called after SSO flows.
* @param options Defines the storage options for the URL; Defaults to Local Storage.
* @returns route called prior to SSO routing to organizations configured IdP.
*/
getPreviousUrl: (options?: StorageOptions) => Promise<string>;
/**
* Store URL in local storage by default, but can be configured. Developed to handle
* SSO routing to organizations configured IdP.
* @param url URL of route
* @param options Defines the storage options for the URL; Defaults to Local Storage.
*/
setPreviousUrl: (url: string, options?: StorageOptions) => Promise<void>;
} }

View File

@@ -37,4 +37,5 @@ export class GlobalState {
enableBrowserIntegrationFingerprint?: boolean; enableBrowserIntegrationFingerprint?: boolean;
enableDuckDuckGoBrowserIntegration?: boolean; enableDuckDuckGoBrowserIntegration?: boolean;
region?: string; region?: string;
previousUrl?: string;
} }

View File

@@ -2828,6 +2828,23 @@ export class StateService<
); );
} }
async getPreviousUrl(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.previousUrl;
}
async setPreviousUrl(url: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
globals.previousUrl = url;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
}
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> { protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
let globals: TGlobalState; let globals: TGlobalState;
if (this.useMemory(options.storageLocation)) { if (this.useMemory(options.storageLocation)) {