add launchSsoBrowserWindow() to default service

This commit is contained in:
rr-bw 2024-09-14 12:05:39 -07:00
parent 66072f66e8
commit da18b42f80
No known key found for this signature in database
GPG Key ID: 3FA13C3ADEE51D5D
9 changed files with 184 additions and 84 deletions

View File

@ -1,73 +1,9 @@
import { inject } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { DefaultLoginService, LoginService } from "@bitwarden/auth/angular"; 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? import { flagEnabled } from "../../../platform/flags"; // TODO-rr-bw: do I need a client specific `flagEnabled()` fn?
export class ExtensionLoginService extends DefaultLoginService implements LoginService { 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 { getShowPasswordlessFlag(): boolean {
return flagEnabled("showPasswordless"); return flagEnabled("showPasswordless");
} }
async launchSsoBrowserWindow(email: string): Promise<void | null> {
// 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),
);
}
} }

View File

@ -27,6 +27,7 @@ import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/a
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; 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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { import {
AutofillSettingsService, AutofillSettingsService,
@ -47,7 +48,10 @@ import {
DefaultAnimationControlService, DefaultAnimationControlService,
} from "@bitwarden/common/platform/abstractions/animation-control.service"; } from "@bitwarden/common/platform/abstractions/animation-control.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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 { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.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 { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { import {
AbstractStorageService, 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 as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault"; import { PasswordRepromptService } from "@bitwarden/vault";
import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
@ -564,7 +572,13 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: LoginService, provide: LoginService,
useClass: ExtensionLoginService, useClass: ExtensionLoginService,
deps: [], deps: [
SsoLoginServiceAbstraction,
PasswordGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction,
EnvironmentService,
PlatformUtilsServiceAbstraction,
],
}), }),
]; ];

View File

@ -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<void | null> {
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"),
);
}
}
}

View File

@ -32,8 +32,10 @@ import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/co
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; 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 { ClientType } from "@bitwarden/common/enums";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; 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 { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -59,6 +61,7 @@ import {
ThemeStateService, ThemeStateService,
} from "@bitwarden/common/platform/theming/theme-state.service"; } from "@bitwarden/common/platform/theming/theme-state.service";
import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; 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 { PolicyListService } from "../admin-console/core/policy-list.service";
import { WebSetPasswordJitService, WebRegistrationFinishService, WebLoginService } from "../auth"; import { WebSetPasswordJitService, WebRegistrationFinishService, WebLoginService } from "../auth";
@ -218,7 +221,13 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: LoginService, provide: LoginService,
useClass: WebLoginService, useClass: WebLoginService,
deps: [], deps: [
SsoLoginServiceAbstraction,
PasswordGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction,
EnvironmentService,
PlatformUtilsServiceAbstraction,
],
}), }),
]; ];

View File

@ -1313,7 +1313,13 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: LoginService, provide: LoginService,
useClass: DefaultLoginService, useClass: DefaultLoginService,
deps: [], deps: [
SsoLoginServiceAbstraction,
PasswordGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction,
EnvironmentService,
PlatformUtilsServiceAbstraction,
],
}), }),
]; ];

View File

@ -1,16 +1,25 @@
import { UrlTree } from "@angular/router"; 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 { export class DefaultLoginService implements LoginService {
async launchSsoBrowserWindow(email: string): Promise<void | null> { constructor(
return null; protected ssoLoginService: SsoLoginServiceAbstraction,
} // TODO-rr-bw: refactor to not use deprecated service
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
getShowPasswordlessFlag(): boolean { protected cryptoFunctionService: CryptoFunctionService,
return null; protected environmentService: EnvironmentService,
} protected platformUtilsService: PlatformUtilsService,
) {}
// Web
setPreviousUrl(route: UrlTree): void | null { setPreviousUrl(route: UrlTree): void | null {
return null; return null;
} }
@ -18,4 +27,70 @@ export class DefaultLoginService implements LoginService {
async getOrgPolicies(): Promise<PasswordPolicies | null> { async getOrgPolicies(): Promise<PasswordPolicies | null> {
return null; return null;
} }
// Web/Browser
getShowPasswordlessFlag(): boolean {
return null;
}
// Used on Browser and overriden on Desktop
async launchSsoBrowserWindow(
email: string,
clientId: "browser" | "desktop",
): Promise<void | null> {
// 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),
);
}
} }

View File

@ -155,7 +155,7 @@
bitButton bitButton
block block
buttonType="secondary" buttonType="secondary"
(click)="launchSsoBrowserWindow()" (click)="launchSsoBrowserWindow('browser')"
> >
<i class="bwi bwi-provider tw-mr-1"></i> <i class="bwi bwi-provider tw-mr-1"></i>
{{ "useSingleSignOn" | i18n }} {{ "useSingleSignOn" | i18n }}

View File

@ -279,8 +279,8 @@ export class LoginComponent implements OnInit, OnDestroy {
} }
} }
protected async launchSsoBrowserWindow(): Promise<void> { protected async launchSsoBrowserWindow(clientId: "browser" | "desktop"): Promise<void> {
await this.loginService.launchSsoBrowserWindow(this.loggedEmail); await this.loginService.launchSsoBrowserWindow(this.loggedEmail, clientId);
} }
protected async goAfterLogIn(userId: UserId): Promise<void> { protected async goAfterLogIn(userId: UserId): Promise<void> {

View File

@ -10,11 +10,13 @@ export interface PasswordPolicies {
} }
export abstract class LoginService { export abstract class LoginService {
// Browser/Desktop
launchSsoBrowserWindow: (email: string) => Promise<void>;
// Web // Web
getShowPasswordlessFlag: () => boolean;
getOrgPolicies: () => Promise<PasswordPolicies | null>; getOrgPolicies: () => Promise<PasswordPolicies | null>;
setPreviousUrl: (route: UrlTree) => void | null; setPreviousUrl: (route: UrlTree) => void | null;
// Web/Browser
getShowPasswordlessFlag: () => boolean;
// Used on Browser and overriden on Desktop
launchSsoBrowserWindow: (email: string, clientId: "browser" | "desktop") => Promise<void>;
} }