From da18b42f80cf18a9de71d13be13c3653447d3205 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:05:39 -0700 Subject: [PATCH] add launchSsoBrowserWindow() to default service --- .../popup/login/extension-login.service.ts | 64 ------------- .../src/popup/services/services.module.ts | 20 +++- .../src/auth/login/desktop-login.service.ts | 58 ++++++++++++ apps/web/src/app/core/core.module.ts | 11 ++- .../src/services/jslib-services.module.ts | 8 +- .../angular/login/default-login.service.ts | 91 +++++++++++++++++-- .../src/angular/login/login.component.html | 2 +- .../auth/src/angular/login/login.component.ts | 4 +- libs/auth/src/angular/login/login.service.ts | 10 +- 9 files changed, 184 insertions(+), 84 deletions(-) create mode 100644 apps/desktop/src/auth/login/desktop-login.service.ts diff --git a/apps/browser/src/auth/popup/login/extension-login.service.ts b/apps/browser/src/auth/popup/login/extension-login.service.ts index 8a94bf5ed1..c9f0594cee 100644 --- a/apps/browser/src/auth/popup/login/extension-login.service.ts +++ b/apps/browser/src/auth/popup/login/extension-login.service.ts @@ -1,73 +1,9 @@ -import { inject } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - import { DefaultLoginService, LoginService } from "@bitwarden/auth/angular"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { flagEnabled } from "../../../platform/flags"; // TODO-rr-bw: do I need a client specific `flagEnabled()` fn? export class ExtensionLoginService extends DefaultLoginService implements LoginService { - ssoLoginService = inject(SsoLoginServiceAbstraction); - // TODO-rr-bw: refactor to not use deprecated service - passwordGenerationService = inject(PasswordGenerationServiceAbstraction); - cryptoFunctionService = inject(CryptoFunctionService); - environmentService = inject(EnvironmentService); - platformUtilsService = inject(PlatformUtilsService); - getShowPasswordlessFlag(): boolean { return flagEnabled("showPasswordless"); } - - async launchSsoBrowserWindow(email: string): Promise { - // Save email for SSO - await this.ssoLoginService.setSsoEmail(email); - - // Generate SSO params - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = - (await this.passwordGenerationService.generatePassword(passwordOptions)) + - ":clientId=browser"; - const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - // Save SSO params - await this.ssoLoginService.setCodeVerifier(codeVerifier); - await this.ssoLoginService.setSsoState(state); - - // Build URL - const env = await firstValueFrom(this.environmentService.environment$); - let url = env.getWebVaultUrl(); - if (url == null) { - url = "https://vault.bitwarden.com"; - } - - const redirectUri = url + "/sso-connector.html"; - - // Launch browser window with URL - this.platformUtilsService.launchUri( - url + - "/#/sso?clientId=browser" + - "&redirectUri=" + - encodeURIComponent(redirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge + - "&email=" + - encodeURIComponent(email), - ); - } } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 51fca5d1b8..260bd23a36 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -27,6 +27,7 @@ import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/a import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AutofillSettingsService, @@ -47,7 +48,10 @@ import { DefaultAnimationControlService, } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { + CryptoFunctionService, + CryptoFunctionService as CryptoFunctionServiceAbstraction, +} from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -56,7 +60,10 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + PlatformUtilsService, + PlatformUtilsService as PlatformUtilsServiceAbstraction, +} from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService, @@ -89,6 +96,7 @@ import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vau import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; @@ -564,7 +572,13 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LoginService, useClass: ExtensionLoginService, - deps: [], + deps: [ + SsoLoginServiceAbstraction, + PasswordGenerationServiceAbstraction, + CryptoFunctionServiceAbstraction, + EnvironmentService, + PlatformUtilsServiceAbstraction, + ], }), ]; diff --git a/apps/desktop/src/auth/login/desktop-login.service.ts b/apps/desktop/src/auth/login/desktop-login.service.ts new file mode 100644 index 0000000000..aec0f13e4b --- /dev/null +++ b/apps/desktop/src/auth/login/desktop-login.service.ts @@ -0,0 +1,58 @@ +import { inject } from "@angular/core"; + +import { DefaultLoginService, LoginService } from "@bitwarden/auth/angular"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; + +export class DesktopLoginService extends DefaultLoginService implements LoginService { + ssoLoginService = inject(SsoLoginServiceAbstraction); + passwordGenerationService = inject(PasswordGenerationServiceAbstraction); + cryptoFunctionService = inject(CryptoFunctionService); + environmentService = inject(EnvironmentService); + platformUtilsService = inject(PlatformUtilsService); + + async launchSsoBrowserWindow( + email: string, + clientId: string, + redirectUri: string, + ): Promise { + if (!ipc.platform.isAppImage && !ipc.platform.isSnapStore && !ipc.platform.isDev) { + return super.launchSsoBrowser(clientId, redirectUri); + } + + // Save email for SSO + await this.ssoLoginService.setSsoEmail(email); + + // Generate SSO params + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + + // Save SSO params + await this.ssoLoginService.setSsoState(state); + await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); + + try { + await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + } catch (err) { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorOccured"), + this.i18nService.t("ssoError"), + ); + } + } +} diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 2dac6779d0..2a7d654b53 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -32,8 +32,10 @@ import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/co import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -59,6 +61,7 @@ import { ThemeStateService, } from "@bitwarden/common/platform/theming/theme-state.service"; import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PolicyListService } from "../admin-console/core/policy-list.service"; import { WebSetPasswordJitService, WebRegistrationFinishService, WebLoginService } from "../auth"; @@ -218,7 +221,13 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LoginService, useClass: WebLoginService, - deps: [], + deps: [ + SsoLoginServiceAbstraction, + PasswordGenerationServiceAbstraction, + CryptoFunctionServiceAbstraction, + EnvironmentService, + PlatformUtilsServiceAbstraction, + ], }), ]; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 164590adbf..71a2ef9cb0 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1313,7 +1313,13 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LoginService, useClass: DefaultLoginService, - deps: [], + deps: [ + SsoLoginServiceAbstraction, + PasswordGenerationServiceAbstraction, + CryptoFunctionServiceAbstraction, + EnvironmentService, + PlatformUtilsServiceAbstraction, + ], }), ]; diff --git a/libs/auth/src/angular/login/default-login.service.ts b/libs/auth/src/angular/login/default-login.service.ts index 6a7151023c..b6ec1e8d54 100644 --- a/libs/auth/src/angular/login/default-login.service.ts +++ b/libs/auth/src/angular/login/default-login.service.ts @@ -1,16 +1,25 @@ import { UrlTree } from "@angular/router"; +import { firstValueFrom } from "rxjs"; -import { LoginService, PasswordPolicies } from "./login.service"; +import { LoginService, PasswordPolicies } from "@bitwarden/auth/angular"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; export class DefaultLoginService implements LoginService { - async launchSsoBrowserWindow(email: string): Promise { - return null; - } - - getShowPasswordlessFlag(): boolean { - return null; - } + constructor( + protected ssoLoginService: SsoLoginServiceAbstraction, + // TODO-rr-bw: refactor to not use deprecated service + protected passwordGenerationService: PasswordGenerationServiceAbstraction, + protected cryptoFunctionService: CryptoFunctionService, + protected environmentService: EnvironmentService, + protected platformUtilsService: PlatformUtilsService, + ) {} + // Web setPreviousUrl(route: UrlTree): void | null { return null; } @@ -18,4 +27,70 @@ export class DefaultLoginService implements LoginService { async getOrgPolicies(): Promise { return null; } + + // Web/Browser + getShowPasswordlessFlag(): boolean { + return null; + } + + // Used on Browser and overriden on Desktop + async launchSsoBrowserWindow( + email: string, + clientId: "browser" | "desktop", + ): Promise { + // Save email for SSO + await this.ssoLoginService.setSsoEmail(email); + + // Generate SSO params + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + + let state = await this.passwordGenerationService.generatePassword(passwordOptions); + // TODO-rr-bw: verify this is correct. Pulling this from original browser login component launchSsoBrowser method + if (clientId === "browser") { + state += ":clientId=browser"; + } + + const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + + // Save SSO params + await this.ssoLoginService.setSsoState(state); + await this.ssoLoginService.setCodeVerifier(codeVerifier); + + // Build URL + const env = await firstValueFrom(this.environmentService.environment$); + let url = env.getWebVaultUrl(); + // TODO-rr-bw: verify this is correct. Pulling this from original browser login component launchSsoBrowser method + if (url == null) { + url = "https://vault.bitwarden.com"; + } + + const redirectUri = + clientId === "browser" + ? url + "/sso-connector.html" // Browser + : "bitwarden://sso-callback"; // Desktop + + // Launch browser window with URL + this.platformUtilsService.launchUri( + url + + "/#/sso?clientId=" + + clientId + + "&redirectUri=" + + encodeURIComponent(redirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge + + "&email=" + + encodeURIComponent(email), + ); + } } diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index a52ca84f84..be81cf8e86 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -155,7 +155,7 @@ bitButton block buttonType="secondary" - (click)="launchSsoBrowserWindow()" + (click)="launchSsoBrowserWindow('browser')" > {{ "useSingleSignOn" | i18n }} diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index f6dc4ccc31..c155bdd704 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -279,8 +279,8 @@ export class LoginComponent implements OnInit, OnDestroy { } } - protected async launchSsoBrowserWindow(): Promise { - await this.loginService.launchSsoBrowserWindow(this.loggedEmail); + protected async launchSsoBrowserWindow(clientId: "browser" | "desktop"): Promise { + await this.loginService.launchSsoBrowserWindow(this.loggedEmail, clientId); } protected async goAfterLogIn(userId: UserId): Promise { diff --git a/libs/auth/src/angular/login/login.service.ts b/libs/auth/src/angular/login/login.service.ts index 331ac92839..f28ba1aa68 100644 --- a/libs/auth/src/angular/login/login.service.ts +++ b/libs/auth/src/angular/login/login.service.ts @@ -10,11 +10,13 @@ export interface PasswordPolicies { } export abstract class LoginService { - // Browser/Desktop - launchSsoBrowserWindow: (email: string) => Promise; - // Web - getShowPasswordlessFlag: () => boolean; getOrgPolicies: () => Promise; setPreviousUrl: (route: UrlTree) => void | null; + + // Web/Browser + getShowPasswordlessFlag: () => boolean; + + // Used on Browser and overriden on Desktop + launchSsoBrowserWindow: (email: string, clientId: "browser" | "desktop") => Promise; }