1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

refactor(active-user-state-refactor): [PM-12040] Remove ActiveUserStatus For SSO Login Component (#13149)

* refactor(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - First pass of work to update the state. In the middle of testing.

* fix(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Fix for jslib-services.module.ts

* fix(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Fix main.background.ts

* test(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Added simple tests

* fix(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Tiny touchups.

* fix(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Few fixes to resolve comments.

* fix(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Changed place where userId is loaded.

* test(active-user-state-refactor): [PM-12040] Remove ActiveUserState from SSO Service - Fixed test.
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-02-04 13:31:36 -05:00
committed by GitHub
parent b55468e6a1
commit 0523ce0b40
15 changed files with 199 additions and 44 deletions

View File

@@ -809,7 +809,7 @@ export default class MainBackground {
this.apiService,
);
this.ssoLoginService = new SsoLoginService(this.stateProvider);
this.ssoLoginService = new SsoLoginService(this.stateProvider, this.logService);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);

View File

@@ -195,7 +195,7 @@ export class BaseLoginDecryptionOptionsComponentV1 implements OnInit, OnDestroy
async loadNewUserData() {
const autoEnrollStatus$ = defer(() =>
this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(),
this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeAccountId),
).pipe(
switchMap((organizationIdentifier) => {
if (organizationIdentifier == undefined) {

View File

@@ -47,7 +47,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<void>;
successRoute = "vault";
userId: UserId;
activeUserId: UserId;
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
ForceSetPasswordReason = ForceSetPasswordReason;
@@ -96,10 +96,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
await this.syncService.fullSync(true);
this.syncLoading = false;
this.userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(this.userId),
this.masterPasswordService.forceSetPasswordReason$(this.activeUserId),
);
this.route.queryParams
@@ -111,7 +111,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
} else {
// Try to get orgSsoId from state as fallback
// Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario.
return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier();
return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeUserId);
}
}),
filter((orgSsoId) => orgSsoId != null),
@@ -167,10 +167,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
// in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one
const existingUserPrivateKey = (await firstValueFrom(
this.keyService.userPrivateKey$(this.userId),
this.keyService.userPrivateKey$(this.activeUserId),
)) as Uint8Array;
const existingUserPublicKey = await firstValueFrom(
this.keyService.userPublicKey$(this.userId),
this.keyService.userPublicKey$(this.activeUserId),
);
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
@@ -217,7 +217,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
this.userId,
this.activeUserId,
resetRequest,
);
});
@@ -260,7 +260,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.None,
this.userId,
this.activeUserId,
);
// User now has a password so update account decryption options in state
@@ -269,9 +269,9 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
);
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(this.userId, this.kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, this.userId);
await this.keyService.setUserKey(userKey[0], this.userId);
await this.kdfConfigService.setKdfConfig(this.activeUserId, this.kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, this.activeUserId);
await this.keyService.setUserKey(userKey[0], this.activeUserId);
// Set private key only for new JIT provisioned users in MP encryption orgs
// Existing TDE users will have private key set on sync or on login
@@ -280,7 +280,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
this.forceSetPasswordReason !=
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission
) {
await this.keyService.setPrivateKey(keyPair[1].encryptedString, this.userId);
await this.keyService.setPrivateKey(keyPair[1].encryptedString, this.activeUserId);
}
const localMasterKeyHash = await this.keyService.hashMasterKey(
@@ -288,6 +288,6 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
masterKey,
HashPurpose.LocalAuthorization,
);
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.userId);
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.activeUserId);
}
}

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { first } from "rxjs/operators";
@@ -27,6 +28,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@@ -55,6 +57,7 @@ export class SsoComponent implements OnInit {
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
protected activeUserId: UserId;
constructor(
protected ssoLoginService: SsoLoginServiceAbstraction,
@@ -74,7 +77,11 @@ export class SsoComponent implements OnInit {
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {}
) {
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
}
async ngOnInit() {
// eslint-disable-next-line rxjs/no-async-subscribe
@@ -226,7 +233,10 @@ export class SsoComponent implements OnInit {
// - TDE login decryption options component
// - Browser SSO on extension open
// Note: you cannot set this in state before 2FA b/c there won't be an account in state.
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier);
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
orgSsoIdentifier,
this.activeUserId,
);
// Users enrolled in admin acct recovery can be forced to set a new password after
// having the admin set a temp password for them (affects TDE & standard users)

View File

@@ -69,7 +69,7 @@
</a>
</div>
<div class="text-center">
<a bitLink href="#" appStopClick (click)="selectOtherTwofactorMethod()">{{
<a bitLink href="#" appStopClick (click)="selectOtherTwoFactorMethod()">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
</div>

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, Inject, OnInit, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { ActivatedRoute, NavigationExtras, Router, RouterLink } from "@angular/router";
import { Subject, takeUntil, lastValueFrom, first, firstValueFrom } from "rxjs";
@@ -31,6 +32,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
@@ -126,6 +128,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
protected changePasswordRoute = "set-password";
protected forcePasswordResetRoute = "update-temp-password";
protected successRoute = "vault";
protected activeUserId: UserId;
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
@@ -148,6 +151,10 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService, toastService);
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
}
async ngOnInit() {
@@ -214,7 +221,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
}
}
async selectOtherTwofactorMethod() {
async selectOtherTwoFactorMethod() {
const dialogRef = TwoFactorOptionsComponent.open(this.dialogService);
const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed);
if (response.result === TwoFactorOptionsDialogResult.Provider) {
@@ -262,7 +269,10 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
// Save off the OrgSsoIdentifier for use in the TDE flows
// - TDE login decryption options component
// - Browser SSO on extension open
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier);
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
this.orgIdentifier,
this.activeUserId,
);
this.loginEmailService.clearValues();
// note: this flow affects both TDE & standard users

View File

@@ -35,6 +35,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { CaptchaProtectedComponent } from "./captcha-protected.component";
@@ -73,6 +74,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected successRoute = "vault";
protected twoFactorTimeoutRoute = "authentication-timeout";
protected activeUserId: UserId;
get isDuoProvider(): boolean {
return (
this.selectedProviderType === TwoFactorProviderType.Duo ||
@@ -102,8 +105,13 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService, toastService);
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
// Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired
this.loginStrategyService.authenticationSessionTimeout$
.pipe(takeUntilDestroyed())
@@ -287,7 +295,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
// Save off the OrgSsoIdentifier for use in the TDE flows
// - TDE login decryption options component
// - Browser SSO on extension open
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier);
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
this.orgIdentifier,
this.activeUserId,
);
this.loginEmailService.clearValues();
// note: this flow affects both TDE & standard users

View File

@@ -799,7 +799,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: SsoLoginServiceAbstraction,
useClass: SsoLoginService,
deps: [StateProvider],
deps: [StateProvider, LogService],
}),
safeProvider({
provide: STATE_FACTORY,

View File

@@ -202,7 +202,7 @@ export class LoginDecryptionOptionsComponent implements OnInit {
});
const autoEnrollStatus$ = defer(() =>
this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(),
this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeAccountId),
).pipe(
switchMap((organizationIdentifier) => {
if (organizationIdentifier == undefined) {

View File

@@ -36,6 +36,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
@@ -89,6 +90,7 @@ export class SsoComponent implements OnInit {
protected state: string | undefined;
protected codeChallenge: string | undefined;
protected clientId: SsoClientType | undefined;
protected activeUserId: UserId | undefined;
formPromise: Promise<AuthResult> | undefined;
initiateSsoFormPromise: Promise<SsoPreValidateResponse> | undefined;
@@ -130,6 +132,8 @@ export class SsoComponent implements OnInit {
}
async ngOnInit() {
this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const qParams: QueryParams = await firstValueFrom(this.route.queryParams);
// This if statement will pass on the second portion of the SSO flow
@@ -384,7 +388,10 @@ export class SsoComponent implements OnInit {
// - TDE login decryption options component
// - Browser SSO on extension open
// Note: you cannot set this in state before 2FA b/c there won't be an account in state.
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier);
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
orgSsoIdentifier,
this.activeUserId,
);
// Users enrolled in admin acct recovery can be forced to set a new password after
// having the admin set a temp password for them (affects TDE & standard users)

View File

@@ -1,5 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { UserId } from "@bitwarden/common/types/guid";
export abstract class SsoLoginServiceAbstraction {
/**
* Gets the code verifier used for SSO.
@@ -74,12 +76,16 @@ export abstract class SsoLoginServiceAbstraction {
* 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.
* @param userId The user id for retrieving the org identifier state.
*/
getActiveUserOrganizationSsoIdentifier: () => Promise<string>;
getActiveUserOrganizationSsoIdentifier: (userId: UserId) => 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>;
setActiveUserOrganizationSsoIdentifier: (
organizationIdentifier: string,
userId: UserId | undefined,
) => Promise<void>;
}

View File

@@ -0,0 +1,94 @@
import { mock, MockProxy } from "jest-mock-extended";
import {
CODE_VERIFIER,
GLOBAL_ORGANIZATION_SSO_IDENTIFIER,
SSO_EMAIL,
SSO_STATE,
SsoLoginService,
USER_ORGANIZATION_SSO_IDENTIFIER,
} from "@bitwarden/common/auth/services/sso-login.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
describe("SSOLoginService ", () => {
let sut: SsoLoginService;
let accountService: FakeAccountService;
let mockSingleUserStateProvider: FakeStateProvider;
let mockLogService: MockProxy<LogService>;
let userId: UserId;
beforeEach(() => {
jest.clearAllMocks();
userId = Utils.newGuid() as UserId;
accountService = mockAccountServiceWith(userId);
mockSingleUserStateProvider = new FakeStateProvider(accountService);
mockLogService = mock<LogService>();
sut = new SsoLoginService(mockSingleUserStateProvider, mockLogService);
});
it("instantiates", () => {
expect(sut).not.toBeFalsy();
});
it("gets and sets code verifier", async () => {
const codeVerifier = "test-code-verifier";
await sut.setCodeVerifier(codeVerifier);
mockSingleUserStateProvider.getGlobal(CODE_VERIFIER);
const result = await sut.getCodeVerifier();
expect(result).toBe(codeVerifier);
});
it("gets and sets SSO state", async () => {
const ssoState = "test-sso-state";
await sut.setSsoState(ssoState);
mockSingleUserStateProvider.getGlobal(SSO_STATE);
const result = await sut.getSsoState();
expect(result).toBe(ssoState);
});
it("gets and sets organization SSO identifier", async () => {
const orgIdentifier = "test-org-identifier";
await sut.setOrganizationSsoIdentifier(orgIdentifier);
mockSingleUserStateProvider.getGlobal(GLOBAL_ORGANIZATION_SSO_IDENTIFIER);
const result = await sut.getOrganizationSsoIdentifier();
expect(result).toBe(orgIdentifier);
});
it("gets and sets SSO email", async () => {
const email = "test@example.com";
await sut.setSsoEmail(email);
mockSingleUserStateProvider.getGlobal(SSO_EMAIL);
const result = await sut.getSsoEmail();
expect(result).toBe(email);
});
it("gets and sets active user organization SSO identifier", async () => {
const userId = Utils.newGuid() as UserId;
const orgIdentifier = "test-active-org-identifier";
await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, userId);
mockSingleUserStateProvider.getUser(userId, USER_ORGANIZATION_SSO_IDENTIFIER);
const result = await sut.getActiveUserOrganizationSsoIdentifier(userId);
expect(result).toBe(orgIdentifier);
});
it("logs error when setting active user organization SSO identifier with undefined userId", async () => {
const orgIdentifier = "test-active-org-identifier";
await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, undefined);
expect(mockLogService.warning).toHaveBeenCalledWith(
"Tried to set a user organization sso identifier with an undefined user id.",
);
});
});

View File

@@ -2,10 +2,13 @@
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import {
ActiveUserState,
GlobalState,
KeyDefinition,
SingleUserState,
SSO_DISK,
StateProvider,
UserKeyDefinition,
@@ -15,21 +18,21 @@ import { SsoLoginServiceAbstraction } from "../abstractions/sso-login.service.ab
/**
* Uses disk storage so that the code verifier can be persisted across sso redirects.
*/
const CODE_VERIFIER = new KeyDefinition<string>(SSO_DISK, "ssoCodeVerifier", {
export 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", {
export 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 USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition<string>(
export const USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition<string>(
SSO_DISK,
"organizationSsoIdentifier",
{
@@ -41,7 +44,7 @@ const USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition<string>(
/**
* Uses disk storage so that the organization sso identifier can be persisted across sso redirects.
*/
const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition<string>(
export const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition<string>(
SSO_DISK,
"organizationSsoIdentifier",
{
@@ -52,7 +55,7 @@ const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition<string>(
/**
* Uses disk storage so that the user's email can be persisted across sso redirects.
*/
const SSO_EMAIL = new KeyDefinition<string>(SSO_DISK, "ssoEmail", {
export const SSO_EMAIL = new KeyDefinition<string>(SSO_DISK, "ssoEmail", {
deserializer: (state) => state,
});
@@ -61,16 +64,15 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
private ssoState: GlobalState<string>;
private orgSsoIdentifierState: GlobalState<string>;
private ssoEmailState: GlobalState<string>;
private activeUserOrgSsoIdentifierState: ActiveUserState<string>;
constructor(private stateProvider: StateProvider) {
constructor(
private stateProvider: StateProvider,
private logService: LogService,
) {
this.codeVerifierState = this.stateProvider.getGlobal(CODE_VERIFIER);
this.ssoState = this.stateProvider.getGlobal(SSO_STATE);
this.orgSsoIdentifierState = this.stateProvider.getGlobal(GLOBAL_ORGANIZATION_SSO_IDENTIFIER);
this.ssoEmailState = this.stateProvider.getGlobal(SSO_EMAIL);
this.activeUserOrgSsoIdentifierState = this.stateProvider.getActive(
USER_ORGANIZATION_SSO_IDENTIFIER,
);
}
getCodeVerifier(): Promise<string> {
@@ -105,11 +107,24 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.ssoEmailState.update((_) => email);
}
getActiveUserOrganizationSsoIdentifier(): Promise<string> {
return firstValueFrom(this.activeUserOrgSsoIdentifierState.state$);
getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise<string> {
return firstValueFrom(this.userOrgSsoIdentifierState(userId).state$);
}
async setActiveUserOrganizationSsoIdentifier(organizationIdentifier: string): Promise<void> {
await this.activeUserOrgSsoIdentifierState.update((_) => organizationIdentifier);
async setActiveUserOrganizationSsoIdentifier(
organizationIdentifier: string,
userId: UserId | undefined,
): Promise<void> {
if (userId === undefined) {
this.logService.warning(
"Tried to set a user organization sso identifier with an undefined user id.",
);
return;
}
await this.userOrgSsoIdentifierState(userId).update((_) => organizationIdentifier);
}
private userOrgSsoIdentifierState(userId: UserId): SingleUserState<string> {
return this.stateProvider.getUser(userId, USER_ORGANIZATION_SSO_IDENTIFIER);
}
}

View File

@@ -9,7 +9,7 @@ import { StateUpdateOptions } from "./state-update-options";
export interface GlobalState<T> {
/**
* Method for allowing you to manipulate state in an additive way.
* @param configureState callback for how you want manipulate this section of state
* @param configureState callback for how you want to manipulate this section of state
* @param options Defaults given by @see {module:state-update-options#DEFAULT_OPTIONS}
* @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true
* @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null

View File

@@ -16,6 +16,7 @@ export interface UserState<T> {
}
export const activeMarker: unique symbol = Symbol("active");
export interface ActiveUserState<T> extends UserState<T> {
readonly [activeMarker]: true;
@@ -32,7 +33,7 @@ export interface ActiveUserState<T> extends UserState<T> {
* @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true
* @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null
* @param options.msTimeout A timeout for how long you are willing to wait for a `combineLatestWith` option to complete. Defaults to 1000ms. Only applies if `combineLatestWith` is set.
*
* @returns A promise that must be awaited before your next action to ensure the update has been written to state.
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
@@ -41,6 +42,7 @@ export interface ActiveUserState<T> extends UserState<T> {
options?: StateUpdateOptions<T, TCombine>,
) => Promise<[UserId, T]>;
}
export interface SingleUserState<T> extends UserState<T> {
readonly userId: UserId;
@@ -51,7 +53,7 @@ export interface SingleUserState<T> extends UserState<T> {
* @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true
* @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null
* @param options.msTimeout A timeout for how long you are willing to wait for a `combineLatestWith` option to complete. Defaults to 1000ms. Only applies if `combineLatestWith` is set.
*
* @returns A promise that must be awaited before your next action to ensure the update has been written to state.
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/