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

[PM-17751] Store SSO email in state on web client (#13295)

* Moved saving of SSO email outside of browser/desktop code

* Clarified comments.

* Tests

* Refactored login component services to manage state

* Fixed input on login component

* Fixed tests

* Linting

* Moved web setting in state into web override

* updated tests

* Fixed typing.

* Fixed type safety issues.

* Added comments and renamed for clarity.

* Removed method parameters that weren't used

* Added clarifying comments

* Added more comments.

* Removed test that is not necessary on base

* Test cleanup

* More comments.

* Linting

* Fixed test.

* Fixed base URL

* Fixed typechecking.

* Type checking

* Moved setting of email state to default service

* Added comments.

* Consolidated SSO URL formatting

* Updated comment

* Fixed reference.

* Fixed missing parameter.

* Initialized service.

* Added comments

* Added initialization of new service

* Made email optional due to CLI.

* Fixed comment on handleSsoClick.

* Added SSO email persistence to v1 component.

---------

Co-authored-by: Bernd Schoolmann <mail@quexten.com>
This commit is contained in:
Todd Martin
2025-02-21 17:09:50 -05:00
committed by GitHub
parent 9dd2033081
commit 077e0f89cc
26 changed files with 534 additions and 245 deletions

View File

@@ -1,7 +1,5 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
import { LoginComponentService } from "@bitwarden/auth/angular";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ClientType } from "@bitwarden/common/enums";
@@ -21,19 +19,55 @@ export class DefaultLoginComponentService implements LoginComponentService {
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected ssoLoginService: SsoLoginServiceAbstraction,
) {}
) {
this.clientType = this.platformUtilsService.getClientType();
}
isLoginWithPasskeySupported(): boolean {
return this.clientType === ClientType.Web;
}
async launchSsoBrowserWindow(
email: string,
clientId: "browser" | "desktop",
): Promise<void | null> {
// Save email for SSO
/**
* Redirects the user to the SSO login page, either via route or in a new browser window.
* @param email The email address of the user attempting to log in
*/
async redirectToSsoLogin(email: string): Promise<void | null> {
// Set the state that we'll need to verify the SSO login when we get the code back
const [state, codeChallenge] = await this.setSsoPreLoginState();
// Set the email address in state. This is used in 2 places:
// 1. On the web client, on the SSO component we need the email address to look up
// the org SSO identifier. The email address is passed via query param for the other clients.
// 2. On all clients, after authentication on the originating client the SSO component
// will need to look up 2FA Remember token by email.
await this.ssoLoginService.setSsoEmail(email);
// Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service.
await this.redirectToSso(email, state, codeChallenge);
}
/**
* No-op implementation of redirectToSso
*/
protected async redirectToSso(
email: string,
state: string,
codeChallenge: string,
): Promise<void> {
return;
}
/**
* No-op implementation of showBackButton
*/
showBackButton(showBackButton: boolean): void {
return;
}
/**
* Sets the state required for verifying SSO login after completion
*/
private async setSsoPreLoginState(): Promise<[string, string]> {
// Generate SSO params
const passwordOptions: any = {
type: "password",
@@ -46,8 +80,8 @@ export class DefaultLoginComponentService implements LoginComponentService {
let state = await this.passwordGenerationService.generatePassword(passwordOptions);
if (clientId === "browser") {
// Need to persist the clientId in the state for the extension
// For the browser extension, we persist the clientId on state so that it will be included after SSO in the callback
if (this.clientType === ClientType.Browser) {
state += ":clientId=browser";
}
@@ -59,35 +93,6 @@ export class DefaultLoginComponentService implements LoginComponentService {
await this.ssoLoginService.setSsoState(state);
await this.ssoLoginService.setCodeVerifier(codeVerifier);
// Build URL
const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl();
const redirectUri =
clientId === "browser"
? webVaultUrl + "/sso-connector.html" // Browser
: "bitwarden://sso-callback"; // Desktop
// Launch browser window with URL
this.platformUtilsService.launchUri(
webVaultUrl +
"/#/sso?clientId=" +
clientId +
"&redirectUri=" +
encodeURIComponent(redirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge +
"&email=" +
encodeURIComponent(email),
);
}
/**
* No-op implementation of showBackButton
*/
showBackButton(showBackButton: boolean): void {
return;
return [state, codeChallenge];
}
}