mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 01:03:35 +00:00
[PM-5364] Create SSO Login Service and add state ownership (#7485)
* create sso service * rename sso service to sso-login service * rename service * add references to sso login service and update state calls * fix browser * fix desktop * return promises * remove sso state from account and global objects * more descriptive org sso identifier method names * fix sso tests * fix tests
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
export abstract class SsoLoginServiceAbstraction {
|
||||
/**
|
||||
* Gets the code verifier used for SSO.
|
||||
*
|
||||
* PKCE requires a `code_verifier` to be generated which is then used to derive a `code_challenge`.
|
||||
* While the `code_challenge` is verified upon return from the SSO provider, the `code_verifier` is
|
||||
* sent to the server with the `authorization_code` so that the server can derive the same `code_challenge`
|
||||
* and verify it matches the one sent in the request for the `authorization_code`.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7636
|
||||
* @returns The code verifier used for SSO.
|
||||
*/
|
||||
getCodeVerifier: () => Promise<string>;
|
||||
/**
|
||||
* Sets the code verifier used for SSO.
|
||||
*
|
||||
* PKCE requires a `code_verifier` to be generated which is then used to derive a `code_challenge`.
|
||||
* While the `code_challenge` is verified upon return from the SSO provider, the `code_verifier` is
|
||||
* sent to the server with the `authorization_code` so that the server can derive the same `code_challenge`
|
||||
* and verify it matches the one sent in the request for the `authorization_code`.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7636
|
||||
*/
|
||||
setCodeVerifier: (codeVerifier: string) => Promise<void>;
|
||||
/**
|
||||
* Gets the value of the SSO state.
|
||||
*
|
||||
* `state` is a parameter used in the Authorization Code Flow of OAuth 2.0 to prevent CSRF attacks. It is an
|
||||
* opaque value generated on the client and is sent to the authorization server. The authorization server
|
||||
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
|
||||
* @returns The SSO state.
|
||||
*/
|
||||
getSsoState: () => Promise<string>;
|
||||
/**
|
||||
* Sets the value of the SSO state.
|
||||
*
|
||||
* `state` is a parameter used in the Authorization Code Flow of OAuth 2.0 to prevent CSRF attacks. It is an
|
||||
* opaque value generated on the client and is sent to the authorization server. The authorization server
|
||||
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
|
||||
*/
|
||||
setSsoState: (ssoState: string) => Promise<void>;
|
||||
/**
|
||||
* Gets the value of the user's organization sso identifier.
|
||||
*
|
||||
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
|
||||
* Do not use this value outside of the SSO login flow.
|
||||
* @returns The user's organization identifier.
|
||||
*/
|
||||
getOrganizationSsoIdentifier: () => Promise<string>;
|
||||
/**
|
||||
* Sets the value of the user's organization sso identifier.
|
||||
*
|
||||
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
|
||||
* Do not use this value outside of the SSO login flow.
|
||||
*/
|
||||
setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
|
||||
/**
|
||||
* Gets the value of the active user's organization sso identifier.
|
||||
*
|
||||
* This should only be used post successful SSO login once the user is initialized.
|
||||
*/
|
||||
getActiveUserOrganizationSsoIdentifier: () => Promise<string>;
|
||||
/**
|
||||
* Sets the value of the active user's organization sso identifier.
|
||||
*
|
||||
* This should only be used post successful SSO login once the user is initialized.
|
||||
*/
|
||||
setActiveUserOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
|
||||
}
|
||||
82
libs/common/src/auth/services/sso-login.service.ts
Normal file
82
libs/common/src/auth/services/sso-login.service.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
ActiveUserState,
|
||||
GlobalState,
|
||||
KeyDefinition,
|
||||
SSO_DISK,
|
||||
StateProvider,
|
||||
} from "../../platform/state";
|
||||
|
||||
/**
|
||||
* Uses disk storage so that the code verifier can be persisted across sso redirects.
|
||||
*/
|
||||
const CODE_VERIFIER = new KeyDefinition<string>(SSO_DISK, "ssoCodeVerifier", {
|
||||
deserializer: (codeVerifier) => codeVerifier,
|
||||
});
|
||||
|
||||
/**
|
||||
* Uses disk storage so that the sso state can be persisted across sso redirects.
|
||||
*/
|
||||
const SSO_STATE = new KeyDefinition<string>(SSO_DISK, "ssoState", {
|
||||
deserializer: (state) => state,
|
||||
});
|
||||
|
||||
/**
|
||||
* Uses disk storage so that the organization sso identifier can be persisted across sso redirects.
|
||||
*/
|
||||
const ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition<string>(
|
||||
SSO_DISK,
|
||||
"organizationSsoIdentifier",
|
||||
{
|
||||
deserializer: (organizationIdentifier) => organizationIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
export class SsoLoginService {
|
||||
private codeVerifierState: GlobalState<string>;
|
||||
private ssoState: GlobalState<string>;
|
||||
private orgSsoIdentifierState: GlobalState<string>;
|
||||
private activeUserOrgSsoIdentifierState: ActiveUserState<string>;
|
||||
|
||||
constructor(private stateProvider: StateProvider) {
|
||||
this.codeVerifierState = this.stateProvider.getGlobal(CODE_VERIFIER);
|
||||
this.ssoState = this.stateProvider.getGlobal(SSO_STATE);
|
||||
this.orgSsoIdentifierState = this.stateProvider.getGlobal(ORGANIZATION_SSO_IDENTIFIER);
|
||||
this.activeUserOrgSsoIdentifierState = this.stateProvider.getActive(
|
||||
ORGANIZATION_SSO_IDENTIFIER,
|
||||
);
|
||||
}
|
||||
|
||||
getCodeVerifier(): Promise<string> {
|
||||
return firstValueFrom(this.codeVerifierState.state$);
|
||||
}
|
||||
|
||||
async setCodeVerifier(codeVerifier: string): Promise<void> {
|
||||
await this.codeVerifierState.update((_) => codeVerifier);
|
||||
}
|
||||
|
||||
getSsoState(): Promise<string> {
|
||||
return firstValueFrom(this.ssoState.state$);
|
||||
}
|
||||
|
||||
async setSsoState(ssoState: string): Promise<void> {
|
||||
await this.ssoState.update((_) => ssoState);
|
||||
}
|
||||
|
||||
getOrganizationSsoIdentifier(): Promise<string> {
|
||||
return firstValueFrom(this.orgSsoIdentifierState.state$);
|
||||
}
|
||||
|
||||
async setOrganizationSsoIdentifier(organizationIdentifier: string): Promise<void> {
|
||||
await this.orgSsoIdentifierState.update((_) => organizationIdentifier);
|
||||
}
|
||||
|
||||
getActiveUserOrganizationSsoIdentifier(): Promise<string> {
|
||||
return firstValueFrom(this.activeUserOrgSsoIdentifierState.state$);
|
||||
}
|
||||
|
||||
async setActiveUserOrganizationSsoIdentifier(organizationIdentifier: string): Promise<void> {
|
||||
await this.activeUserOrgSsoIdentifierState.update((_) => organizationIdentifier);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user