diff --git a/apps/browser/src/auth/guards/fido2-auth.guard.ts b/apps/browser/src/auth/guards/fido2-auth.guard.ts new file mode 100644 index 00000000000..03dd575aaad --- /dev/null +++ b/apps/browser/src/auth/guards/fido2-auth.guard.ts @@ -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; +}; diff --git a/apps/browser/src/platform/popup/services/browser-router.service.ts b/apps/browser/src/platform/popup/services/browser-router.service.ts new file mode 100644 index 00000000000..23cbd7b8614 --- /dev/null +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -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; + } +} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 002337eb70b..adc7a5c0afe 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -11,6 +11,7 @@ import { import { canAccessFeature } from "@bitwarden/angular/guard/feature-flag.guard"; 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 { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; @@ -72,17 +73,19 @@ const routes: Routes = [ path: "home", component: HomeComponent, canActivate: [UnauthGuard], - data: { state: "home" }, + data: { state: "home", doNotSaveUrl: true }, }, { path: "fido2", component: Fido2Component, + canActivate: [fido2AuthGuard], + data: { state: "fido2" }, }, { path: "login", component: LoginComponent, canActivate: [UnauthGuard], - data: { state: "login" }, + data: { state: "login", doNotSaveUrl: true }, }, { path: "login-with-device", @@ -100,13 +103,13 @@ const routes: Routes = [ path: "lock", component: LockComponent, canActivate: [lockGuard()], - data: { state: "lock" }, + data: { state: "lock", doNotSaveUrl: true }, }, { path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard], - data: { state: "2fa" }, + data: { state: "2fa", doNotSaveUrl: true }, }, { path: "2fa-options", diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 4a2b515b74a..f02df000602 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -524,4 +524,17 @@ export abstract class StateService { value: Record>, options?: StorageOptions ) => Promise; + /** + * 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; + /** + * 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; } diff --git a/libs/common/src/platform/models/domain/global-state.ts b/libs/common/src/platform/models/domain/global-state.ts index dfe3c6c417f..a43bf246bd8 100644 --- a/libs/common/src/platform/models/domain/global-state.ts +++ b/libs/common/src/platform/models/domain/global-state.ts @@ -37,4 +37,5 @@ export class GlobalState { enableBrowserIntegrationFingerprint?: boolean; enableDuckDuckGoBrowserIntegration?: boolean; region?: string; + previousUrl?: string; } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 14ba4abfd39..174a953dfe6 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -2828,6 +2828,23 @@ export class StateService< ); } + async getPreviousUrl(options?: StorageOptions): Promise { + return ( + await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.previousUrl; + } + + async setPreviousUrl(url: string, options?: StorageOptions): Promise { + 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 { let globals: TGlobalState; if (this.useMemory(options.storageLocation)) {