From e6e088fd02c2b23c9085289f49ff246fa94d0e22 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Tue, 25 Jul 2023 15:45:11 -0400 Subject: [PATCH] Initial work --- apps/browser/src/auth/popup/home.component.ts | 22 +++++++++++++++--- apps/browser/src/auth/popup/lock.component.ts | 8 ++++--- .../browser/src/background/main.background.ts | 1 + .../browser-fido2-user-interface.service.ts | 21 +++++++++++++++++ .../popup/components/fido2/fido2.component.ts | 17 +++++++++++++- apps/desktop/src/auth/lock.component.ts | 5 ++-- apps/web/src/app/auth/lock.component.ts | 8 ++++--- .../src/auth/components/lock.component.ts | 23 ++++++++++++++++--- .../src/auth/components/login.component.ts | 15 +++++++++++- .../auth/components/two-factor.component.ts | 12 ++++++++++ ...ido2-user-interface.service.abstraction.ts | 1 + .../fido2/fido2-authenticator.service.ts | 9 ++++++++ 12 files changed, 126 insertions(+), 16 deletions(-) diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index 5dd3bdd641a..a75b21838c6 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; @@ -20,6 +20,9 @@ export class HomeComponent implements OnInit, OnDestroy { private destroyed$: Subject = new Subject(); loginInitiated = false; + //use this to redirect somehwere else after login + redirectPath: string; + sessionId: string; formGroup = this.formBuilder.group({ email: ["", [Validators.required, Validators.email]], rememberEmail: [false], @@ -32,10 +35,16 @@ export class HomeComponent implements OnInit, OnDestroy { private router: Router, private i18nService: I18nService, private environmentService: EnvironmentService, - private loginService: LoginService + private loginService: LoginService, + protected route: ActivatedRoute ) {} async ngOnInit(): Promise { + this.route?.queryParams.pipe(takeUntil(this.destroyed$)).subscribe((params) => { + this.redirectPath = params?.redirectPath; + this.sessionId = params?.sessionId; + }); + let savedEmail = this.loginService.getEmail(); const rememberEmail = this.loginService.getRememberEmail(); @@ -80,7 +89,14 @@ export class HomeComponent implements OnInit, OnDestroy { this.loginService.setEmail(this.formGroup.value.email); this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); - this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } }); + + this.router.navigate(["login"], { + queryParams: { + email: this.formGroup.value.email, + redirectPath: this.redirectPath, + sessionId: this.sessionId, + }, + }); } get selfHostedDomain() { diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts index d13609f61d4..45d30c96598 100644 --- a/apps/browser/src/auth/popup/lock.component.ts +++ b/apps/browser/src/auth/popup/lock.component.ts @@ -1,5 +1,5 @@ import { Component, NgZone } from "@angular/core"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; @@ -50,7 +50,8 @@ export class LockComponent extends BaseLockComponent { policyService: InternalPolicyService, passwordStrengthService: PasswordStrengthServiceAbstraction, private authService: AuthService, - dialogService: DialogServiceAbstraction + dialogService: DialogServiceAbstraction, + route: ActivatedRoute ) { super( router, @@ -69,7 +70,8 @@ export class LockComponent extends BaseLockComponent { policyApiService, policyService, passwordStrengthService, - dialogService + dialogService, + route ); this.successRoute = "/tabs/current"; this.isInitialLockScreen = (window as any).previousPopupUrl == null; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index acef263d699..a69517a1f1f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -532,6 +532,7 @@ export default class MainBackground { this.fido2AuthenticatorService = new Fido2AuthenticatorService( this.cipherService, this.fido2UserInterfaceService, + this.authService, this.logService ); this.fido2ClientService = new Fido2ClientService( diff --git a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts index 2c2901c7d53..4f630f84e57 100644 --- a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts @@ -94,6 +94,14 @@ export type BrowserFido2Message = { sessionId: string } & ( type: "AbortResponse"; fallbackRequested: boolean; } + | { + type: "LogInRequest"; + userVerification: boolean; + } + | { + type: "LogInResponse"; + userVerified: boolean; + } ); export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { @@ -272,6 +280,19 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi await this.receive("AbortResponse"); } + async login(userVerification: boolean): Promise<{ userVerified: boolean }> { + const data: BrowserFido2Message = { + type: "LogInRequest", + userVerification, + sessionId: this.sessionId, + }; + + await this.send(data); + const response = await this.receive("LogInResponse"); + + return { userVerified: response.userVerified }; + } + async close() { this.popupUtilsService.closePopOut(this.popout); this.closed = true; diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts index 751f0aa7c69..f1edddf17b9 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts @@ -19,6 +19,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2KeyView } from "@bitwarden/common/vault/models/view/fido2-key.view"; import { BrowserApi } from "../../../../platform/browser/browser-api"; +import { PopupUtilsService } from "../../../../popup/services/popup-utils.service"; import { BrowserFido2Message, BrowserFido2UserInterfaceSession, @@ -48,7 +49,8 @@ export class Fido2Component implements OnInit, OnDestroy { constructor( private activatedRoute: ActivatedRoute, private cipherService: CipherService, - private passwordRepromptService: PasswordRepromptService + private passwordRepromptService: PasswordRepromptService, + private popupUtils: PopupUtilsService ) {} ngOnInit(): void { @@ -61,6 +63,19 @@ export class Fido2Component implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe(([sessionId, message]) => { this.sessionId = sessionId; + if (message.type === "LogInRequest") { + //route to login + const url = chrome.extension.getURL( + `popup/index.html#/home?sessionId=${sessionId}&redirectPath=fido2&uilocation=popout` + ); + // BrowserApi.createNewWindow(url); + BrowserApi.createNewTab(url); + // if (this.popupUtils.inPopup(window)) { + // BrowserApi.closePopup(window); + // } + return; + } + if (message.type === "NewSessionCreatedRequest" && message.sessionId !== sessionId) { return this.abort(false); } diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts index 61f7b4bfe30..6a693e40158 100644 --- a/apps/desktop/src/auth/lock.component.ts +++ b/apps/desktop/src/auth/lock.component.ts @@ -44,7 +44,7 @@ export class LockComponent extends BaseLockComponent { environmentService: EnvironmentService, protected override stateService: ElectronStateService, apiService: ApiService, - private route: ActivatedRoute, + route: ActivatedRoute, private broadcasterService: BroadcasterService, ngZone: NgZone, policyApiService: PolicyApiServiceAbstraction, @@ -71,7 +71,8 @@ export class LockComponent extends BaseLockComponent { policyApiService, policyService, passwordStrengthService, - dialogService + dialogService, + route ); } diff --git a/apps/web/src/app/auth/lock.component.ts b/apps/web/src/app/auth/lock.component.ts index 789ebc16955..ac5896554eb 100644 --- a/apps/web/src/app/auth/lock.component.ts +++ b/apps/web/src/app/auth/lock.component.ts @@ -1,5 +1,5 @@ import { Component, NgZone } from "@angular/core"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; @@ -43,7 +43,8 @@ export class LockComponent extends BaseLockComponent { policyApiService: PolicyApiServiceAbstraction, policyService: InternalPolicyService, passwordStrengthService: PasswordStrengthServiceAbstraction, - dialogService: DialogServiceAbstraction + dialogService: DialogServiceAbstraction, + route: ActivatedRoute ) { super( router, @@ -62,7 +63,8 @@ export class LockComponent extends BaseLockComponent { policyApiService, policyService, passwordStrengthService, - dialogService + dialogService, + route ); } diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 01c083ee5a9..7b68a0eb297 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -1,5 +1,5 @@ import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, Subject } from "rxjs"; import { concatMap, take, takeUntil } from "rxjs/operators"; @@ -41,6 +41,8 @@ export class LockComponent implements OnInit, OnDestroy { biometricLock: boolean; biometricText: string; hideInput: boolean; + redirectPath: string; + sessionId: string; protected successRoute = "vault"; protected forcePasswordResetRoute = "update-temp-password"; @@ -70,10 +72,21 @@ export class LockComponent implements OnInit, OnDestroy { protected policyApiService: PolicyApiServiceAbstraction, protected policyService: InternalPolicyService, protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected dialogService: DialogServiceAbstraction + protected dialogService: DialogServiceAbstraction, + protected route: ActivatedRoute ) {} async ngOnInit() { + this.route?.queryParams.subscribe((params) => { + this.redirectPath = params?.redirectPath; + this.sessionId = params?.sessionId; + }); + + //use redirectPath to redirect to a specific page after successful login + if (this.redirectPath) { + this.successRoute = this.redirectPath; + } + this.stateService.activeAccount$ .pipe( concatMap(async () => { @@ -291,7 +304,11 @@ export class LockComponent implements OnInit, OnDestroy { if (this.onSuccessfulSubmit != null) { await this.onSuccessfulSubmit(); } else if (this.router != null) { - this.router.navigate([this.successRoute]); + this.router.navigate([this.successRoute], { + queryParams: { + sessionId: this.sessionId, + }, + }); } } diff --git a/libs/angular/src/auth/components/login.component.ts b/libs/angular/src/auth/components/login.component.ts index f5a7a27af46..aed29659f42 100644 --- a/libs/angular/src/auth/components/login.component.ts +++ b/libs/angular/src/auth/components/login.component.ts @@ -39,6 +39,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit showLoginWithDevice: boolean; validatedEmail = false; paramEmailSet = false; + redirectPath: string; + sessionId: string; formGroup = this.formBuilder.group({ email: ["", [Validators.required, Validators.email]], @@ -83,11 +85,17 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit this.route?.queryParams.subscribe((params) => { if (params != null) { const queryParamsEmail = params["email"]; + this.redirectPath = params?.["redirectPath"]; + this.sessionId = params?.["sessionId"]; if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) { this.formGroup.get("email").setValue(queryParamsEmail); this.loginService.setEmail(queryParamsEmail); this.paramEmailSet = true; } + //use redirectPath to redirect to a specific page after successful login + if (this.redirectPath) { + this.successRoute = this.redirectPath; + } } }); let email = this.loginService.getEmail(); @@ -142,7 +150,12 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit if (this.onSuccessfulLoginTwoFactorNavigate != null) { this.onSuccessfulLoginTwoFactorNavigate(); } else { - this.router.navigate([this.twoFactorRoute]); + this.router.navigate([this.twoFactorRoute], { + queryParams: { + redirectPath: this.redirectPath, + sessionId: this.sessionId, + }, + }); } } else if (response.forcePasswordReset != ForceResetPasswordReason.None) { if (this.onSuccessfulLoginForceResetNavigate != null) { diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index fa7aa66f9f5..d0890ba05ab 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -39,6 +39,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI formPromise: Promise; emailPromise: Promise; identifier: string = null; + redirectPath: string; + sessionId: string; onSuccessfulLogin: () => Promise; onSuccessfulLoginNavigate: () => Promise; @@ -74,6 +76,15 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI if (qParams.identifier != null) { this.identifier = qParams.identifier; } + + if (qParams.redirectPath != null) { + this.redirectPath = qParams.redirectPath; + this.successRoute = this.redirectPath; + } + + if (qParams.sessionId != null) { + this.sessionId = qParams?.sessionId; + } }); if (this.needsLock) { @@ -220,6 +231,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.router.navigate([this.successRoute], { queryParams: { identifier: this.identifier, + sessionId: this.sessionId, }, }); } diff --git a/libs/common/src/vault/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/vault/abstractions/fido2/fido2-user-interface.service.abstraction.ts index 3d0db42d3c1..f804e154310 100644 --- a/libs/common/src/vault/abstractions/fido2/fido2-user-interface.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/fido2/fido2-user-interface.service.abstraction.ts @@ -37,5 +37,6 @@ export abstract class Fido2UserInterfaceSession { abortController?: AbortController ) => Promise; informCredentialNotFound: (abortController?: AbortController) => Promise; + login: (userVerification: boolean) => Promise<{ userVerified: boolean }>; close: () => void; } diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts index c586b24679e..df947dbcee2 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts @@ -3,6 +3,7 @@ import { CBOR } from "cbor-redux"; import { LogService } from "../../../platform/abstractions/log.service"; import { Utils } from "../../../platform/misc/utils"; import { CipherService } from "../../abstractions/cipher.service"; +import { AuthService } from "../../../auth/abstractions/auth.service"; import { Fido2AlgorithmIdentifier, Fido2AutenticatorError, @@ -21,6 +22,7 @@ import { Fido2KeyView } from "../../models/view/fido2-key.view"; import { joseToDer } from "./ecdsa-utils"; import { Fido2Utils } from "./fido2-utils"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; // AAGUID: 6e8248d5-b479-40db-a3d8-11116f7e8349 export const AAGUID = new Uint8Array([ @@ -37,6 +39,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr constructor( private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, + private authService: AuthService, private logService?: LogService ) {} async makeCredential( @@ -220,6 +223,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr ); try { + const authStatus = await this.authService.getAuthStatus(); + + if (authStatus === AuthenticationStatus.LoggedOut) { + const response = await userInterfaceSession.login(params.requireUserVerification); + } + if ( params.requireUserVerification != undefined && typeof params.requireUserVerification !== "boolean"