mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 10:43:35 +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:
@@ -1,11 +1,18 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { DefaultLoginComponentService } from "@bitwarden/auth/angular";
|
||||
import { SsoUrlService } from "@bitwarden/auth/common";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import {
|
||||
Environment,
|
||||
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 { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service";
|
||||
@@ -18,6 +25,7 @@ jest.mock("../../../platform/flags", () => ({
|
||||
}));
|
||||
|
||||
describe("ExtensionLoginComponentService", () => {
|
||||
const baseUrl = "https://webvault.bitwarden.com";
|
||||
let service: ExtensionLoginComponentService;
|
||||
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
|
||||
let environmentService: MockProxy<EnvironmentService>;
|
||||
@@ -25,13 +33,20 @@ describe("ExtensionLoginComponentService", () => {
|
||||
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
|
||||
let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||
let extensionAnonLayoutWrapperDataService: MockProxy<ExtensionAnonLayoutWrapperDataService>;
|
||||
let ssoUrlService: MockProxy<SsoUrlService>;
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
passwordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
||||
platformUtilsService = mock<BrowserPlatformUtilsService>();
|
||||
ssoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||
ssoUrlService = mock<SsoUrlService>();
|
||||
extensionAnonLayoutWrapperDataService = mock<ExtensionAnonLayoutWrapperDataService>();
|
||||
environmentService.environment$ = new BehaviorSubject<Environment>({
|
||||
getWebVaultUrl: () => baseUrl,
|
||||
} as Environment);
|
||||
platformUtilsService.getClientType.mockReturnValue(ClientType.Browser);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
@@ -44,6 +59,7 @@ describe("ExtensionLoginComponentService", () => {
|
||||
platformUtilsService,
|
||||
ssoLoginService,
|
||||
extensionAnonLayoutWrapperDataService,
|
||||
ssoUrlService,
|
||||
),
|
||||
},
|
||||
{ provide: DefaultLoginComponentService, useExisting: ExtensionLoginComponentService },
|
||||
@@ -52,6 +68,11 @@ describe("ExtensionLoginComponentService", () => {
|
||||
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
||||
{ provide: PlatformUtilsService, useValue: platformUtilsService },
|
||||
{ provide: SsoLoginServiceAbstraction, useValue: ssoLoginService },
|
||||
{
|
||||
provide: ExtensionAnonLayoutWrapperDataService,
|
||||
useValue: extensionAnonLayoutWrapperDataService,
|
||||
},
|
||||
{ provide: SsoUrlService, useValue: ssoUrlService },
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(ExtensionLoginComponentService);
|
||||
@@ -61,6 +82,26 @@ describe("ExtensionLoginComponentService", () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("redirectToSso", () => {
|
||||
it("launches SSO browser window", async () => {
|
||||
const email = "test@bitwarden.com";
|
||||
const state = "testState";
|
||||
const expectedState = "testState:clientId=browser";
|
||||
const codeVerifier = "testCodeVerifier";
|
||||
const codeChallenge = "testCodeChallenge";
|
||||
|
||||
passwordGenerationService.generatePassword.mockResolvedValueOnce(state);
|
||||
passwordGenerationService.generatePassword.mockResolvedValueOnce(codeVerifier);
|
||||
jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue(codeChallenge);
|
||||
|
||||
await service.redirectToSsoLogin(email);
|
||||
|
||||
expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(expectedState);
|
||||
expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier);
|
||||
expect(platformUtilsService.launchUri).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("showBackButton", () => {
|
||||
it("sets showBackButton in extensionAnonLayoutWrapperDataService", () => {
|
||||
service.showBackButton(true);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { DefaultLoginComponentService, LoginComponentService } from "@bitwarden/auth/angular";
|
||||
import { SsoUrlService } from "@bitwarden/auth/common";
|
||||
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";
|
||||
@@ -23,6 +25,7 @@ export class ExtensionLoginComponentService
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
private extensionAnonLayoutWrapperDataService: ExtensionAnonLayoutWrapperDataService,
|
||||
private ssoUrlService: SsoUrlService,
|
||||
) {
|
||||
super(
|
||||
cryptoFunctionService,
|
||||
@@ -31,7 +34,35 @@ export class ExtensionLoginComponentService
|
||||
platformUtilsService,
|
||||
ssoLoginService,
|
||||
);
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
}
|
||||
|
||||
/**
|
||||
* On the extension, redirecting to the SSO login page is done via a new browser window, opened
|
||||
* to the SSO component on the web client.
|
||||
* @param email the email of the user trying to log in, used to look up the org SSO identifier
|
||||
* @param state the state that will be used to verify the SSO login, which needs to be passed to the IdP
|
||||
* @param codeChallenge the challenge that will be verified after the code is returned from the IdP, which needs to be passed to the IdP
|
||||
*/
|
||||
protected override async redirectToSso(
|
||||
email: string,
|
||||
state: string,
|
||||
codeChallenge: string,
|
||||
): Promise<void> {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVaultUrl = env.getWebVaultUrl();
|
||||
|
||||
const redirectUri = webVaultUrl + "/sso-connector.html";
|
||||
|
||||
const webAppSsoUrl = this.ssoUrlService.buildSsoUrl(
|
||||
webVaultUrl,
|
||||
this.clientType,
|
||||
redirectUri,
|
||||
state,
|
||||
codeChallenge,
|
||||
email,
|
||||
);
|
||||
|
||||
this.platformUtilsService.launchUri(webAppSsoUrl);
|
||||
}
|
||||
|
||||
showBackButton(showBackButton: boolean): void {
|
||||
|
||||
@@ -27,7 +27,12 @@ import {
|
||||
LoginDecryptionOptionsService,
|
||||
SsoComponentService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import {
|
||||
LockService,
|
||||
LoginEmailService,
|
||||
PinServiceAbstraction,
|
||||
SsoUrlService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@@ -550,6 +555,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useExisting: ExtensionAnonLayoutWrapperDataService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SsoUrlService,
|
||||
useClass: SsoUrlService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: LoginComponentService,
|
||||
useClass: ExtensionLoginComponentService,
|
||||
@@ -560,6 +570,7 @@ const safeProviders: SafeProvider[] = [
|
||||
PlatformUtilsService,
|
||||
SsoLoginServiceAbstraction,
|
||||
ExtensionAnonLayoutWrapperDataService,
|
||||
SsoUrlService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
Reference in New Issue
Block a user