@phila/sso-core 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -236,14 +236,13 @@ class SSOClient {
|
|
|
236
236
|
async signOut(options) {
|
|
237
237
|
this.assertInitialized();
|
|
238
238
|
this.log("Initiating sign-out...");
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
};
|
|
239
|
+
const authority = this._state.activePolicy ? this.provider.getAuthority(this._state.activePolicy) : this.provider.getAuthority();
|
|
240
|
+
await this.msalInstance.clearCache();
|
|
241
|
+
const msalAuthConfig = this.msalInstance.getConfiguration().auth;
|
|
242
|
+
const postLogoutRedirectUri = options?.postLogoutRedirectUri ?? msalAuthConfig.postLogoutRedirectUri ?? window.location.origin;
|
|
244
243
|
this.updateState({ isLoading: true });
|
|
245
244
|
this.events.emit("auth:signedOut", void 0);
|
|
246
|
-
|
|
245
|
+
window.location.href = `${authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`;
|
|
247
246
|
}
|
|
248
247
|
async forgotPassword() {
|
|
249
248
|
this.assertInitialized();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/events.ts","../src/constants.ts","../src/state-encoding.ts","../src/providers/b2c.ts","../src/client.ts","../src/providers/ciam.ts","../src/providers/entra.ts"],"sourcesContent":["import type { SSOEventMap, SSOEventName } from \"./types\";\n\ntype Listener<T> = (data: T) => void;\n\nexport class SSOEventEmitter {\n private listeners = new Map<SSOEventName, Set<Listener<unknown>>>();\n\n on<K extends SSOEventName>(event: K, listener: Listener<SSOEventMap[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener<unknown>);\n\n // Return unsubscribe function\n return () => {\n set.delete(listener as Listener<unknown>);\n };\n }\n\n emit<K extends SSOEventName>(event: K, data: SSOEventMap[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n for (const listener of set) {\n listener(data);\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","export const DEFAULT_POLICIES = {\n SIGN_UP_SIGN_IN: \"B2C_1A_SIGNUP_SIGNIN\",\n SIGN_IN_ONLY: \"B2C_1A_AD_SIGNIN_ONLY\",\n RESET_PASSWORD: \"B2C_1A_PASSWORDRESET\",\n} as const;\n\nexport const DEFAULT_SCOPES = {\n OPENID: \"openid\",\n PROFILE: \"profile\",\n OFFLINE_ACCESS: \"offline_access\",\n} as const;\n\nexport const CACHE_CONFIG = {\n LOCATION: \"sessionStorage\" as const,\n STORE_AUTH_STATE_IN_COOKIE: false,\n} as const;\n\nexport const MSAL_ERROR_CODES = {\n USER_CANCELLED: \"user_cancelled\",\n NO_CACHED_AUTHORITY: \"no_cached_authority_error\",\n INTERACTION_REQUIRED: \"interaction_required\",\n FORGOT_PASSWORD: \"AADB2C90118\",\n} as const;\n\nexport const STATE_SEPARATOR = \"|\";\n","import { STATE_SEPARATOR } from \"./constants\";\n\n/**\n * Encode a custom state object into a base64 string that can be\n * appended to the MSAL state parameter using a pipe separator.\n */\nexport function encodeState(stateObj: Record<string, unknown>): string {\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Decode a base64-encoded state string back into an object.\n */\nexport function decodeState(encoded: string): Record<string, unknown> | null {\n try {\n return JSON.parse(atob(encoded));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract custom postback data from the MSAL state string.\n * MSAL prepends its own GUID before the pipe separator.\n */\nexport function extractCustomState(msalState: string | undefined): Record<string, unknown> | null {\n if (!msalState) return null;\n\n const parts = msalState.split(STATE_SEPARATOR);\n if (parts.length < 2) return null;\n\n // Everything after the first pipe is our encoded state\n const customPart = parts.slice(1).join(STATE_SEPARATOR);\n return decodeState(customPart);\n}\n","import { LogLevel } from \"@azure/msal-browser\";\nimport type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, B2CProviderConfig, B2CPolicies } from \"../types\";\nimport { DEFAULT_POLICIES, DEFAULT_SCOPES, CACHE_CONFIG } from \"../constants\";\n\nexport class B2CProvider implements IAuthProvider {\n readonly type = \"b2c\" as const;\n\n private readonly clientId: string;\n private readonly b2cEnv: string;\n private readonly authorityDomain: string;\n private readonly redirectUri: string;\n private readonly postLogoutRedirectUri: string;\n private readonly policies: B2CPolicies;\n private readonly apiScopes: string[];\n private readonly cacheLocation: \"sessionStorage\" | \"localStorage\";\n\n constructor(config: B2CProviderConfig) {\n this.clientId = config.clientId;\n this.b2cEnv = config.b2cEnvironment;\n this.authorityDomain = config.authorityDomain;\n this.redirectUri = config.redirectUri;\n this.postLogoutRedirectUri = config.postLogoutRedirectUri ?? config.redirectUri;\n this.policies = {\n signUpSignIn: config.policies?.signUpSignIn ?? DEFAULT_POLICIES.SIGN_UP_SIGN_IN,\n signInOnly: config.policies?.signInOnly ?? DEFAULT_POLICIES.SIGN_IN_ONLY,\n resetPassword: config.policies?.resetPassword ?? DEFAULT_POLICIES.RESET_PASSWORD,\n };\n this.apiScopes = config.apiScopes ?? [];\n this.cacheLocation = config.cacheLocation ?? CACHE_CONFIG.LOCATION;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.clientId,\n authority: this.getAuthority(this.policies.signUpSignIn),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.redirectUri,\n postLogoutRedirectUri: this.postLogoutRedirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.cacheLocation,\n storeAuthStateInCookie: CACHE_CONFIG.STORE_AUTH_STATE_IN_COOKIE,\n },\n system: {\n loggerOptions: {\n logLevel: LogLevel.Warning,\n loggerCallback: (_level: LogLevel, message: string) => {\n console.warn(\"[sso-core/b2c]\", message);\n },\n },\n },\n };\n }\n\n getAuthority(policy?: string): string {\n const p = policy ?? this.policies.signUpSignIn;\n return `https://${this.authorityDomain}/${this.b2cEnv}.onmicrosoft.com/${p}`;\n }\n\n getKnownAuthorities(): string[] {\n return [this.authorityDomain];\n }\n\n identifyPolicy(response: AuthenticationResult): string | null {\n const claims = response.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n\n const acr = (claims.acr ?? claims.tfp) as string | undefined;\n return acr?.toUpperCase() ?? null;\n }\n\n getDefaultScopes(): string[] {\n return [DEFAULT_SCOPES.OPENID, DEFAULT_SCOPES.PROFILE];\n }\n\n getApiScopes(): string[] {\n return this.apiScopes;\n }\n\n /** Get the authority URL for the sign-in-only (city employee) policy */\n getSignInOnlyAuthority(): string {\n return this.getAuthority(this.policies.signInOnly);\n }\n\n /** Get the authority URL for the password reset policy */\n getResetPasswordAuthority(): string {\n return this.getAuthority(this.policies.resetPassword);\n }\n\n /** Get all configured policy names */\n getPolicies(): Readonly<B2CPolicies> {\n return this.policies;\n }\n}\n","import { PublicClientApplication, InteractionRequiredAuthError } from \"@azure/msal-browser\";\nimport type { AccountInfo, AuthenticationResult } from \"@azure/msal-browser\";\nimport { SSOEventEmitter } from \"./events\";\nimport { encodeState, extractCustomState } from \"./state-encoding\";\nimport { MSAL_ERROR_CODES } from \"./constants\";\nimport { B2CProvider } from \"./providers/b2c\";\nimport type {\n IAuthProvider,\n SSOClientConfig,\n SSOClientState,\n SignInOptions,\n SignOutOptions,\n TokenOptions,\n AuthResponse,\n} from \"./types\";\n\nconst INITIAL_STATE: SSOClientState = {\n isAuthenticated: false,\n isLoading: false,\n user: null,\n token: null,\n error: null,\n activePolicy: null,\n authReady: false,\n};\n\nexport class SSOClient {\n readonly events = new SSOEventEmitter();\n private readonly provider: IAuthProvider;\n private readonly debug: boolean;\n private readonly encodedState: string | null;\n\n private msalInstance: PublicClientApplication | null = null;\n private _state: SSOClientState = { ...INITIAL_STATE };\n\n constructor(config: SSOClientConfig) {\n this.provider = config.provider;\n this.debug = config.debug ?? false;\n this.encodedState = config.state ? encodeState(config.state) : null;\n }\n\n get state(): Readonly<SSOClientState> {\n return this._state;\n }\n\n // ── Lifecycle ──\n\n async initialize(): Promise<AuthResponse | null> {\n this.log(\"Initializing SSOClient...\");\n this.updateState({ isLoading: true });\n\n const msalConfig = this.provider.buildMsalConfig();\n this.msalInstance = new PublicClientApplication(msalConfig);\n\n await this.msalInstance.initialize();\n\n const result = await this.handleRedirect();\n\n this.updateState({ isLoading: false, authReady: true });\n return result;\n }\n\n destroy(): void {\n this.events.removeAllListeners();\n this.msalInstance = null;\n this._state = { ...INITIAL_STATE };\n }\n\n // ── Auth Actions ──\n\n async handleRedirect(): Promise<AuthResponse | null> {\n this.assertInitialized();\n this.log(\"Handling redirect promise...\");\n\n try {\n const response = await this.msalInstance!.handleRedirectPromise();\n\n if (!response) {\n // No redirect response — check for existing sessions\n this.selectAccount(null);\n return null;\n }\n\n this.log(\"Redirect response received\", response);\n\n // Extract custom postback state\n const customState = extractCustomState(response.state);\n\n // Identify which policy was used via the acr/tfp claim\n const policy = this.provider.identifyPolicy(response);\n this.updateState({ activePolicy: policy });\n\n // Check if this was a forgot-password completion\n if (this.isForgotPasswordPolicy(policy)) {\n this.log(\"Forgot password flow completed\");\n this.events.emit(\"auth:forgotPassword\", undefined as never);\n return { ...response, customPostbackObject: customState ?? undefined } as AuthResponse;\n }\n\n // Normal sign-in flow\n this.updateState({ isLoading: true });\n this.selectAccount(policy);\n await this.acquireTokenAfterRedirect(response);\n\n const authResponse: AuthResponse = {\n ...response,\n customPostbackObject: customState ?? undefined,\n };\n\n this.events.emit(\"auth:signedIn\", authResponse);\n return authResponse;\n } catch (error) {\n return this.handleRedirectError(error);\n }\n }\n\n async signIn(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-in...\");\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:loading\", true);\n\n const request = this.buildLoginRequest(this.provider.getAuthority(), options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signInCityEmployee(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating city employee sign-in...\");\n\n if (!(this.provider instanceof B2CProvider)) {\n // For non-B2C providers, fall back to regular sign-in\n return this.signIn(options);\n }\n\n const authority = this.provider.getSignInOnlyAuthority();\n const request = this.buildLoginRequest(authority, options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signOut(options?: SignOutOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-out...\");\n\n const logoutRequest = {\n account: this._state.user ?? undefined,\n postLogoutRedirectUri: options?.postLogoutRedirectUri,\n authority: this.provider.getAuthority(),\n };\n\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:signedOut\", undefined as never);\n await this.msalInstance!.logoutRedirect(logoutRequest);\n }\n\n async forgotPassword(): Promise<void> {\n this.assertInitialized();\n\n if (!(this.provider instanceof B2CProvider)) {\n this.log(\"Forgot password is only supported for B2C providers\");\n return;\n }\n\n this.log(\"Initiating forgot password flow...\");\n const authority = this.provider.getResetPasswordAuthority();\n await this.msalInstance!.loginRedirect({ scopes: [], authority });\n }\n\n async acquireToken(options?: TokenOptions): Promise<string | null> {\n this.assertInitialized();\n this.log(\"Acquiring token...\");\n\n const account = this._state.user;\n if (!account) {\n this.log(\"No account found, cannot acquire token\");\n return null;\n }\n\n const scopes = options?.scopes ?? this.provider.getApiScopes();\n const tokenRequest = {\n scopes,\n forceRefresh: options?.forceRefresh ?? false,\n account,\n authority: this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority(),\n };\n\n try {\n const response = await this.msalInstance!.acquireTokenSilent(tokenRequest);\n\n if (!response.accessToken) {\n throw new InteractionRequiredAuthError(\"empty_token\");\n }\n\n this.log(\"Token acquired silently\");\n this.updateState({ token: response.accessToken, error: null });\n this.events.emit(\"auth:tokenAcquired\", response.accessToken);\n return response.accessToken;\n } catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n this.log(\"Silent token acquisition failed, falling back to redirect\");\n try {\n await this.msalInstance!.acquireTokenRedirect(tokenRequest);\n return null; // Page will redirect\n } catch (redirectError) {\n this.handleError(redirectError);\n return null;\n }\n }\n this.handleError(error);\n return null;\n }\n }\n\n // ── Internal Helpers ──\n\n private selectAccount(policy: string | null): void {\n const accounts = this.msalInstance!.getAllAccounts();\n\n if (accounts.length === 0) {\n this.updateState({ isAuthenticated: false, user: null });\n return;\n }\n\n let selected: AccountInfo | null = null;\n\n if (accounts.length === 1) {\n selected = accounts[0];\n } else if (policy) {\n // Filter accounts by policy and authority domain\n const filtered = accounts.filter(account => {\n const claims = account.idTokenClaims as Record<string, unknown> | undefined;\n const iss = (claims?.iss as string) ?? \"\";\n const knownAuthorities = this.provider.getKnownAuthorities();\n const matchesAuthority = knownAuthorities.some(auth => iss.toUpperCase().includes(auth.toUpperCase()));\n const matchesPolicy = account.homeAccountId.toUpperCase().includes(policy.toUpperCase());\n return matchesAuthority && matchesPolicy;\n });\n\n if (filtered.length >= 1) {\n selected = filtered[0];\n }\n }\n\n if (!selected && accounts.length > 0) {\n selected = accounts[0];\n }\n\n if (selected) {\n this.log(\"Account selected\", selected.username);\n this.updateState({ isAuthenticated: true, user: selected });\n }\n }\n\n private async acquireTokenAfterRedirect(response: AuthenticationResult): Promise<void> {\n // Set the account first so acquireToken can use it\n const account =\n this.msalInstance!.getAccountByHomeId(response.account?.homeAccountId ?? \"\") ?? response.account ?? null;\n\n if (account) {\n this.updateState({ isAuthenticated: true, user: account });\n }\n\n await this.acquireToken();\n this.updateState({ isLoading: false });\n }\n\n private buildLoginRequest(authority: string, options?: SignInOptions) {\n const scopes = options?.scopes ?? this.provider.getDefaultScopes();\n return {\n scopes,\n authority,\n state: this.encodedState ? `${this.encodedState}` : undefined,\n loginHint: options?.loginHint,\n domainHint: options?.domainHint,\n };\n }\n\n private isForgotPasswordPolicy(policy: string | null): boolean {\n if (!policy) return false;\n if (!(this.provider instanceof B2CProvider)) return false;\n return policy.toUpperCase() === this.provider.getPolicies().resetPassword.toUpperCase();\n }\n\n private handleRedirectError(error: unknown): null {\n const err = error as { errorMessage?: string; errorCode?: string };\n\n // Detect B2C forgot-password error code\n if (err.errorMessage?.includes(MSAL_ERROR_CODES.FORGOT_PASSWORD)) {\n this.log(\"Forgot password error detected, redirecting...\");\n this.forgotPassword();\n return null;\n }\n\n this.handleError(error);\n this.updateState({ isLoading: false });\n return null;\n }\n\n private handleError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"Error:\", err.message);\n this.updateState({ error: err });\n this.events.emit(\"auth:error\", err);\n }\n\n private updateState(partial: Partial<SSOClientState>): void {\n this._state = { ...this._state, ...partial };\n this.events.emit(\"auth:stateChanged\", this._state);\n }\n\n private assertInitialized(): void {\n if (!this.msalInstance) {\n throw new Error(\"SSOClient not initialized. Call initialize() first.\");\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) {\n console.log(\"[sso-core]\", ...args);\n }\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, CIAMProviderConfig } from \"../types\";\n\n/**\n * Entra External ID (CIAM) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class CIAMProvider implements IAuthProvider {\n readonly type = \"ciam\" as const;\n\n private readonly config: CIAMProviderConfig;\n\n constructor(config: CIAMProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://${this.config.tenantSubdomain}.ciamlogin.com/`;\n }\n\n getKnownAuthorities(): string[] {\n return [`${this.config.tenantSubdomain}.ciamlogin.com`];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // CIAM doesn't use client-side policy selection\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, EntraProviderConfig } from \"../types\";\n\n/**\n * Entra workforce (city employees) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class EntraProvider implements IAuthProvider {\n readonly type = \"entra\" as const;\n\n private readonly config: EntraProviderConfig;\n\n constructor(config: EntraProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://login.microsoftonline.com/${this.config.tenantId}`;\n }\n\n getKnownAuthorities(): string[] {\n return [\"login.microsoftonline.com\"];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // Entra workforce doesn't use B2C policies\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n"],"names":["LogLevel","PublicClientApplication","InteractionRequiredAuthError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/events.ts","../src/constants.ts","../src/state-encoding.ts","../src/providers/b2c.ts","../src/client.ts","../src/providers/ciam.ts","../src/providers/entra.ts"],"sourcesContent":["import type { SSOEventMap, SSOEventName } from \"./types\";\n\ntype Listener<T> = (data: T) => void;\n\nexport class SSOEventEmitter {\n private listeners = new Map<SSOEventName, Set<Listener<unknown>>>();\n\n on<K extends SSOEventName>(event: K, listener: Listener<SSOEventMap[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener<unknown>);\n\n // Return unsubscribe function\n return () => {\n set.delete(listener as Listener<unknown>);\n };\n }\n\n emit<K extends SSOEventName>(event: K, data: SSOEventMap[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n for (const listener of set) {\n listener(data);\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","export const DEFAULT_POLICIES = {\n SIGN_UP_SIGN_IN: \"B2C_1A_SIGNUP_SIGNIN\",\n SIGN_IN_ONLY: \"B2C_1A_AD_SIGNIN_ONLY\",\n RESET_PASSWORD: \"B2C_1A_PASSWORDRESET\",\n} as const;\n\nexport const DEFAULT_SCOPES = {\n OPENID: \"openid\",\n PROFILE: \"profile\",\n OFFLINE_ACCESS: \"offline_access\",\n} as const;\n\nexport const CACHE_CONFIG = {\n LOCATION: \"sessionStorage\" as const,\n STORE_AUTH_STATE_IN_COOKIE: false,\n} as const;\n\nexport const MSAL_ERROR_CODES = {\n USER_CANCELLED: \"user_cancelled\",\n NO_CACHED_AUTHORITY: \"no_cached_authority_error\",\n INTERACTION_REQUIRED: \"interaction_required\",\n FORGOT_PASSWORD: \"AADB2C90118\",\n} as const;\n\nexport const STATE_SEPARATOR = \"|\";\n","import { STATE_SEPARATOR } from \"./constants\";\n\n/**\n * Encode a custom state object into a base64 string that can be\n * appended to the MSAL state parameter using a pipe separator.\n */\nexport function encodeState(stateObj: Record<string, unknown>): string {\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Decode a base64-encoded state string back into an object.\n */\nexport function decodeState(encoded: string): Record<string, unknown> | null {\n try {\n return JSON.parse(atob(encoded));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract custom postback data from the MSAL state string.\n * MSAL prepends its own GUID before the pipe separator.\n */\nexport function extractCustomState(msalState: string | undefined): Record<string, unknown> | null {\n if (!msalState) return null;\n\n const parts = msalState.split(STATE_SEPARATOR);\n if (parts.length < 2) return null;\n\n // Everything after the first pipe is our encoded state\n const customPart = parts.slice(1).join(STATE_SEPARATOR);\n return decodeState(customPart);\n}\n","import { LogLevel } from \"@azure/msal-browser\";\nimport type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, B2CProviderConfig, B2CPolicies } from \"../types\";\nimport { DEFAULT_POLICIES, DEFAULT_SCOPES, CACHE_CONFIG } from \"../constants\";\n\nexport class B2CProvider implements IAuthProvider {\n readonly type = \"b2c\" as const;\n\n private readonly clientId: string;\n private readonly b2cEnv: string;\n private readonly authorityDomain: string;\n private readonly redirectUri: string;\n private readonly postLogoutRedirectUri: string;\n private readonly policies: B2CPolicies;\n private readonly apiScopes: string[];\n private readonly cacheLocation: \"sessionStorage\" | \"localStorage\";\n\n constructor(config: B2CProviderConfig) {\n this.clientId = config.clientId;\n this.b2cEnv = config.b2cEnvironment;\n this.authorityDomain = config.authorityDomain;\n this.redirectUri = config.redirectUri;\n this.postLogoutRedirectUri = config.postLogoutRedirectUri ?? config.redirectUri;\n this.policies = {\n signUpSignIn: config.policies?.signUpSignIn ?? DEFAULT_POLICIES.SIGN_UP_SIGN_IN,\n signInOnly: config.policies?.signInOnly ?? DEFAULT_POLICIES.SIGN_IN_ONLY,\n resetPassword: config.policies?.resetPassword ?? DEFAULT_POLICIES.RESET_PASSWORD,\n };\n this.apiScopes = config.apiScopes ?? [];\n this.cacheLocation = config.cacheLocation ?? CACHE_CONFIG.LOCATION;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.clientId,\n authority: this.getAuthority(this.policies.signUpSignIn),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.redirectUri,\n postLogoutRedirectUri: this.postLogoutRedirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.cacheLocation,\n storeAuthStateInCookie: CACHE_CONFIG.STORE_AUTH_STATE_IN_COOKIE,\n },\n system: {\n loggerOptions: {\n logLevel: LogLevel.Warning,\n loggerCallback: (_level: LogLevel, message: string) => {\n console.warn(\"[sso-core/b2c]\", message);\n },\n },\n },\n };\n }\n\n getAuthority(policy?: string): string {\n const p = policy ?? this.policies.signUpSignIn;\n return `https://${this.authorityDomain}/${this.b2cEnv}.onmicrosoft.com/${p}`;\n }\n\n getKnownAuthorities(): string[] {\n return [this.authorityDomain];\n }\n\n identifyPolicy(response: AuthenticationResult): string | null {\n const claims = response.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n\n const acr = (claims.acr ?? claims.tfp) as string | undefined;\n return acr?.toUpperCase() ?? null;\n }\n\n getDefaultScopes(): string[] {\n return [DEFAULT_SCOPES.OPENID, DEFAULT_SCOPES.PROFILE];\n }\n\n getApiScopes(): string[] {\n return this.apiScopes;\n }\n\n /** Get the authority URL for the sign-in-only (city employee) policy */\n getSignInOnlyAuthority(): string {\n return this.getAuthority(this.policies.signInOnly);\n }\n\n /** Get the authority URL for the password reset policy */\n getResetPasswordAuthority(): string {\n return this.getAuthority(this.policies.resetPassword);\n }\n\n /** Get all configured policy names */\n getPolicies(): Readonly<B2CPolicies> {\n return this.policies;\n }\n}\n","import { PublicClientApplication, InteractionRequiredAuthError } from \"@azure/msal-browser\";\nimport type { AccountInfo, AuthenticationResult } from \"@azure/msal-browser\";\nimport { SSOEventEmitter } from \"./events\";\nimport { encodeState, extractCustomState } from \"./state-encoding\";\nimport { MSAL_ERROR_CODES } from \"./constants\";\nimport { B2CProvider } from \"./providers/b2c\";\nimport type {\n IAuthProvider,\n SSOClientConfig,\n SSOClientState,\n SignInOptions,\n SignOutOptions,\n TokenOptions,\n AuthResponse,\n} from \"./types\";\n\nconst INITIAL_STATE: SSOClientState = {\n isAuthenticated: false,\n isLoading: false,\n user: null,\n token: null,\n error: null,\n activePolicy: null,\n authReady: false,\n};\n\nexport class SSOClient {\n readonly events = new SSOEventEmitter();\n private readonly provider: IAuthProvider;\n private readonly debug: boolean;\n private readonly encodedState: string | null;\n\n private msalInstance: PublicClientApplication | null = null;\n private _state: SSOClientState = { ...INITIAL_STATE };\n\n constructor(config: SSOClientConfig) {\n this.provider = config.provider;\n this.debug = config.debug ?? false;\n this.encodedState = config.state ? encodeState(config.state) : null;\n }\n\n get state(): Readonly<SSOClientState> {\n return this._state;\n }\n\n // ── Lifecycle ──\n\n async initialize(): Promise<AuthResponse | null> {\n this.log(\"Initializing SSOClient...\");\n this.updateState({ isLoading: true });\n\n const msalConfig = this.provider.buildMsalConfig();\n this.msalInstance = new PublicClientApplication(msalConfig);\n\n await this.msalInstance.initialize();\n\n const result = await this.handleRedirect();\n\n this.updateState({ isLoading: false, authReady: true });\n return result;\n }\n\n destroy(): void {\n this.events.removeAllListeners();\n this.msalInstance = null;\n this._state = { ...INITIAL_STATE };\n }\n\n // ── Auth Actions ──\n\n async handleRedirect(): Promise<AuthResponse | null> {\n this.assertInitialized();\n this.log(\"Handling redirect promise...\");\n\n try {\n const response = await this.msalInstance!.handleRedirectPromise();\n\n if (!response) {\n // No redirect response — check for existing sessions\n this.selectAccount(null);\n return null;\n }\n\n this.log(\"Redirect response received\", response);\n\n // Extract custom postback state\n const customState = extractCustomState(response.state);\n\n // Identify which policy was used via the acr/tfp claim\n const policy = this.provider.identifyPolicy(response);\n this.updateState({ activePolicy: policy });\n\n // Check if this was a forgot-password completion\n if (this.isForgotPasswordPolicy(policy)) {\n this.log(\"Forgot password flow completed\");\n this.events.emit(\"auth:forgotPassword\", undefined as never);\n return { ...response, customPostbackObject: customState ?? undefined } as AuthResponse;\n }\n\n // Normal sign-in flow\n this.updateState({ isLoading: true });\n this.selectAccount(policy);\n await this.acquireTokenAfterRedirect(response);\n\n const authResponse: AuthResponse = {\n ...response,\n customPostbackObject: customState ?? undefined,\n };\n\n this.events.emit(\"auth:signedIn\", authResponse);\n return authResponse;\n } catch (error) {\n return this.handleRedirectError(error);\n }\n }\n\n async signIn(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-in...\");\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:loading\", true);\n\n const request = this.buildLoginRequest(this.provider.getAuthority(), options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signInCityEmployee(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating city employee sign-in...\");\n\n if (!(this.provider instanceof B2CProvider)) {\n // For non-B2C providers, fall back to regular sign-in\n return this.signIn(options);\n }\n\n const authority = this.provider.getSignInOnlyAuthority();\n const request = this.buildLoginRequest(authority, options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signOut(options?: SignOutOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-out...\");\n\n const authority = this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority();\n\n // logoutRedirect() performs iframe-based silent logout for federated\n // accounts (e.g. Entra ID). Microsoft's endpoints block iframes with\n // X-Frame-Options: deny, causing visible console errors and broken flows.\n // We clear the local MSAL cache and navigate directly to the B2C\n // end_session endpoint instead, which avoids any iframe usage.\n await this.msalInstance!.clearCache();\n\n const msalAuthConfig = this.msalInstance!.getConfiguration().auth;\n const postLogoutRedirectUri =\n options?.postLogoutRedirectUri ??\n (msalAuthConfig.postLogoutRedirectUri as string | undefined) ??\n window.location.origin;\n\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:signedOut\", undefined as never);\n\n window.location.href = `${authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`;\n }\n\n async forgotPassword(): Promise<void> {\n this.assertInitialized();\n\n if (!(this.provider instanceof B2CProvider)) {\n this.log(\"Forgot password is only supported for B2C providers\");\n return;\n }\n\n this.log(\"Initiating forgot password flow...\");\n const authority = this.provider.getResetPasswordAuthority();\n await this.msalInstance!.loginRedirect({ scopes: [], authority });\n }\n\n async acquireToken(options?: TokenOptions): Promise<string | null> {\n this.assertInitialized();\n this.log(\"Acquiring token...\");\n\n const account = this._state.user;\n if (!account) {\n this.log(\"No account found, cannot acquire token\");\n return null;\n }\n\n const scopes = options?.scopes ?? this.provider.getApiScopes();\n const tokenRequest = {\n scopes,\n forceRefresh: options?.forceRefresh ?? false,\n account,\n authority: this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority(),\n };\n\n try {\n const response = await this.msalInstance!.acquireTokenSilent(tokenRequest);\n\n if (!response.accessToken) {\n throw new InteractionRequiredAuthError(\"empty_token\");\n }\n\n this.log(\"Token acquired silently\");\n this.updateState({ token: response.accessToken, error: null });\n this.events.emit(\"auth:tokenAcquired\", response.accessToken);\n return response.accessToken;\n } catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n this.log(\"Silent token acquisition failed, falling back to redirect\");\n try {\n await this.msalInstance!.acquireTokenRedirect(tokenRequest);\n return null; // Page will redirect\n } catch (redirectError) {\n this.handleError(redirectError);\n return null;\n }\n }\n this.handleError(error);\n return null;\n }\n }\n\n // ── Internal Helpers ──\n\n private selectAccount(policy: string | null): void {\n const accounts = this.msalInstance!.getAllAccounts();\n\n if (accounts.length === 0) {\n this.updateState({ isAuthenticated: false, user: null });\n return;\n }\n\n let selected: AccountInfo | null = null;\n\n if (accounts.length === 1) {\n selected = accounts[0];\n } else if (policy) {\n // Filter accounts by policy and authority domain\n const filtered = accounts.filter(account => {\n const claims = account.idTokenClaims as Record<string, unknown> | undefined;\n const iss = (claims?.iss as string) ?? \"\";\n const knownAuthorities = this.provider.getKnownAuthorities();\n const matchesAuthority = knownAuthorities.some(auth => iss.toUpperCase().includes(auth.toUpperCase()));\n const matchesPolicy = account.homeAccountId.toUpperCase().includes(policy.toUpperCase());\n return matchesAuthority && matchesPolicy;\n });\n\n if (filtered.length >= 1) {\n selected = filtered[0];\n }\n }\n\n if (!selected && accounts.length > 0) {\n selected = accounts[0];\n }\n\n if (selected) {\n this.log(\"Account selected\", selected.username);\n this.updateState({ isAuthenticated: true, user: selected });\n }\n }\n\n private async acquireTokenAfterRedirect(response: AuthenticationResult): Promise<void> {\n // Set the account first so acquireToken can use it\n const account =\n this.msalInstance!.getAccountByHomeId(response.account?.homeAccountId ?? \"\") ?? response.account ?? null;\n\n if (account) {\n this.updateState({ isAuthenticated: true, user: account });\n }\n\n await this.acquireToken();\n this.updateState({ isLoading: false });\n }\n\n private buildLoginRequest(authority: string, options?: SignInOptions) {\n const scopes = options?.scopes ?? this.provider.getDefaultScopes();\n return {\n scopes,\n authority,\n state: this.encodedState ? `${this.encodedState}` : undefined,\n loginHint: options?.loginHint,\n domainHint: options?.domainHint,\n };\n }\n\n private isForgotPasswordPolicy(policy: string | null): boolean {\n if (!policy) return false;\n if (!(this.provider instanceof B2CProvider)) return false;\n return policy.toUpperCase() === this.provider.getPolicies().resetPassword.toUpperCase();\n }\n\n private handleRedirectError(error: unknown): null {\n const err = error as { errorMessage?: string; errorCode?: string };\n\n // Detect B2C forgot-password error code\n if (err.errorMessage?.includes(MSAL_ERROR_CODES.FORGOT_PASSWORD)) {\n this.log(\"Forgot password error detected, redirecting...\");\n this.forgotPassword();\n return null;\n }\n\n this.handleError(error);\n this.updateState({ isLoading: false });\n return null;\n }\n\n private handleError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"Error:\", err.message);\n this.updateState({ error: err });\n this.events.emit(\"auth:error\", err);\n }\n\n private updateState(partial: Partial<SSOClientState>): void {\n this._state = { ...this._state, ...partial };\n this.events.emit(\"auth:stateChanged\", this._state);\n }\n\n private assertInitialized(): void {\n if (!this.msalInstance) {\n throw new Error(\"SSOClient not initialized. Call initialize() first.\");\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) {\n console.log(\"[sso-core]\", ...args);\n }\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, CIAMProviderConfig } from \"../types\";\n\n/**\n * Entra External ID (CIAM) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class CIAMProvider implements IAuthProvider {\n readonly type = \"ciam\" as const;\n\n private readonly config: CIAMProviderConfig;\n\n constructor(config: CIAMProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://${this.config.tenantSubdomain}.ciamlogin.com/`;\n }\n\n getKnownAuthorities(): string[] {\n return [`${this.config.tenantSubdomain}.ciamlogin.com`];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // CIAM doesn't use client-side policy selection\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, EntraProviderConfig } from \"../types\";\n\n/**\n * Entra workforce (city employees) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class EntraProvider implements IAuthProvider {\n readonly type = \"entra\" as const;\n\n private readonly config: EntraProviderConfig;\n\n constructor(config: EntraProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://login.microsoftonline.com/${this.config.tenantId}`;\n }\n\n getKnownAuthorities(): string[] {\n return [\"login.microsoftonline.com\"];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // Entra workforce doesn't use B2C policies\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n"],"names":["LogLevel","PublicClientApplication","InteractionRequiredAuthError"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -234,14 +234,13 @@ class SSOClient {
|
|
|
234
234
|
async signOut(options) {
|
|
235
235
|
this.assertInitialized();
|
|
236
236
|
this.log("Initiating sign-out...");
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
};
|
|
237
|
+
const authority = this._state.activePolicy ? this.provider.getAuthority(this._state.activePolicy) : this.provider.getAuthority();
|
|
238
|
+
await this.msalInstance.clearCache();
|
|
239
|
+
const msalAuthConfig = this.msalInstance.getConfiguration().auth;
|
|
240
|
+
const postLogoutRedirectUri = options?.postLogoutRedirectUri ?? msalAuthConfig.postLogoutRedirectUri ?? window.location.origin;
|
|
242
241
|
this.updateState({ isLoading: true });
|
|
243
242
|
this.events.emit("auth:signedOut", void 0);
|
|
244
|
-
|
|
243
|
+
window.location.href = `${authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`;
|
|
245
244
|
}
|
|
246
245
|
async forgotPassword() {
|
|
247
246
|
this.assertInitialized();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/events.ts","../src/constants.ts","../src/state-encoding.ts","../src/providers/b2c.ts","../src/client.ts","../src/providers/ciam.ts","../src/providers/entra.ts"],"sourcesContent":["import type { SSOEventMap, SSOEventName } from \"./types\";\n\ntype Listener<T> = (data: T) => void;\n\nexport class SSOEventEmitter {\n private listeners = new Map<SSOEventName, Set<Listener<unknown>>>();\n\n on<K extends SSOEventName>(event: K, listener: Listener<SSOEventMap[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener<unknown>);\n\n // Return unsubscribe function\n return () => {\n set.delete(listener as Listener<unknown>);\n };\n }\n\n emit<K extends SSOEventName>(event: K, data: SSOEventMap[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n for (const listener of set) {\n listener(data);\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","export const DEFAULT_POLICIES = {\n SIGN_UP_SIGN_IN: \"B2C_1A_SIGNUP_SIGNIN\",\n SIGN_IN_ONLY: \"B2C_1A_AD_SIGNIN_ONLY\",\n RESET_PASSWORD: \"B2C_1A_PASSWORDRESET\",\n} as const;\n\nexport const DEFAULT_SCOPES = {\n OPENID: \"openid\",\n PROFILE: \"profile\",\n OFFLINE_ACCESS: \"offline_access\",\n} as const;\n\nexport const CACHE_CONFIG = {\n LOCATION: \"sessionStorage\" as const,\n STORE_AUTH_STATE_IN_COOKIE: false,\n} as const;\n\nexport const MSAL_ERROR_CODES = {\n USER_CANCELLED: \"user_cancelled\",\n NO_CACHED_AUTHORITY: \"no_cached_authority_error\",\n INTERACTION_REQUIRED: \"interaction_required\",\n FORGOT_PASSWORD: \"AADB2C90118\",\n} as const;\n\nexport const STATE_SEPARATOR = \"|\";\n","import { STATE_SEPARATOR } from \"./constants\";\n\n/**\n * Encode a custom state object into a base64 string that can be\n * appended to the MSAL state parameter using a pipe separator.\n */\nexport function encodeState(stateObj: Record<string, unknown>): string {\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Decode a base64-encoded state string back into an object.\n */\nexport function decodeState(encoded: string): Record<string, unknown> | null {\n try {\n return JSON.parse(atob(encoded));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract custom postback data from the MSAL state string.\n * MSAL prepends its own GUID before the pipe separator.\n */\nexport function extractCustomState(msalState: string | undefined): Record<string, unknown> | null {\n if (!msalState) return null;\n\n const parts = msalState.split(STATE_SEPARATOR);\n if (parts.length < 2) return null;\n\n // Everything after the first pipe is our encoded state\n const customPart = parts.slice(1).join(STATE_SEPARATOR);\n return decodeState(customPart);\n}\n","import { LogLevel } from \"@azure/msal-browser\";\nimport type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, B2CProviderConfig, B2CPolicies } from \"../types\";\nimport { DEFAULT_POLICIES, DEFAULT_SCOPES, CACHE_CONFIG } from \"../constants\";\n\nexport class B2CProvider implements IAuthProvider {\n readonly type = \"b2c\" as const;\n\n private readonly clientId: string;\n private readonly b2cEnv: string;\n private readonly authorityDomain: string;\n private readonly redirectUri: string;\n private readonly postLogoutRedirectUri: string;\n private readonly policies: B2CPolicies;\n private readonly apiScopes: string[];\n private readonly cacheLocation: \"sessionStorage\" | \"localStorage\";\n\n constructor(config: B2CProviderConfig) {\n this.clientId = config.clientId;\n this.b2cEnv = config.b2cEnvironment;\n this.authorityDomain = config.authorityDomain;\n this.redirectUri = config.redirectUri;\n this.postLogoutRedirectUri = config.postLogoutRedirectUri ?? config.redirectUri;\n this.policies = {\n signUpSignIn: config.policies?.signUpSignIn ?? DEFAULT_POLICIES.SIGN_UP_SIGN_IN,\n signInOnly: config.policies?.signInOnly ?? DEFAULT_POLICIES.SIGN_IN_ONLY,\n resetPassword: config.policies?.resetPassword ?? DEFAULT_POLICIES.RESET_PASSWORD,\n };\n this.apiScopes = config.apiScopes ?? [];\n this.cacheLocation = config.cacheLocation ?? CACHE_CONFIG.LOCATION;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.clientId,\n authority: this.getAuthority(this.policies.signUpSignIn),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.redirectUri,\n postLogoutRedirectUri: this.postLogoutRedirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.cacheLocation,\n storeAuthStateInCookie: CACHE_CONFIG.STORE_AUTH_STATE_IN_COOKIE,\n },\n system: {\n loggerOptions: {\n logLevel: LogLevel.Warning,\n loggerCallback: (_level: LogLevel, message: string) => {\n console.warn(\"[sso-core/b2c]\", message);\n },\n },\n },\n };\n }\n\n getAuthority(policy?: string): string {\n const p = policy ?? this.policies.signUpSignIn;\n return `https://${this.authorityDomain}/${this.b2cEnv}.onmicrosoft.com/${p}`;\n }\n\n getKnownAuthorities(): string[] {\n return [this.authorityDomain];\n }\n\n identifyPolicy(response: AuthenticationResult): string | null {\n const claims = response.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n\n const acr = (claims.acr ?? claims.tfp) as string | undefined;\n return acr?.toUpperCase() ?? null;\n }\n\n getDefaultScopes(): string[] {\n return [DEFAULT_SCOPES.OPENID, DEFAULT_SCOPES.PROFILE];\n }\n\n getApiScopes(): string[] {\n return this.apiScopes;\n }\n\n /** Get the authority URL for the sign-in-only (city employee) policy */\n getSignInOnlyAuthority(): string {\n return this.getAuthority(this.policies.signInOnly);\n }\n\n /** Get the authority URL for the password reset policy */\n getResetPasswordAuthority(): string {\n return this.getAuthority(this.policies.resetPassword);\n }\n\n /** Get all configured policy names */\n getPolicies(): Readonly<B2CPolicies> {\n return this.policies;\n }\n}\n","import { PublicClientApplication, InteractionRequiredAuthError } from \"@azure/msal-browser\";\nimport type { AccountInfo, AuthenticationResult } from \"@azure/msal-browser\";\nimport { SSOEventEmitter } from \"./events\";\nimport { encodeState, extractCustomState } from \"./state-encoding\";\nimport { MSAL_ERROR_CODES } from \"./constants\";\nimport { B2CProvider } from \"./providers/b2c\";\nimport type {\n IAuthProvider,\n SSOClientConfig,\n SSOClientState,\n SignInOptions,\n SignOutOptions,\n TokenOptions,\n AuthResponse,\n} from \"./types\";\n\nconst INITIAL_STATE: SSOClientState = {\n isAuthenticated: false,\n isLoading: false,\n user: null,\n token: null,\n error: null,\n activePolicy: null,\n authReady: false,\n};\n\nexport class SSOClient {\n readonly events = new SSOEventEmitter();\n private readonly provider: IAuthProvider;\n private readonly debug: boolean;\n private readonly encodedState: string | null;\n\n private msalInstance: PublicClientApplication | null = null;\n private _state: SSOClientState = { ...INITIAL_STATE };\n\n constructor(config: SSOClientConfig) {\n this.provider = config.provider;\n this.debug = config.debug ?? false;\n this.encodedState = config.state ? encodeState(config.state) : null;\n }\n\n get state(): Readonly<SSOClientState> {\n return this._state;\n }\n\n // ── Lifecycle ──\n\n async initialize(): Promise<AuthResponse | null> {\n this.log(\"Initializing SSOClient...\");\n this.updateState({ isLoading: true });\n\n const msalConfig = this.provider.buildMsalConfig();\n this.msalInstance = new PublicClientApplication(msalConfig);\n\n await this.msalInstance.initialize();\n\n const result = await this.handleRedirect();\n\n this.updateState({ isLoading: false, authReady: true });\n return result;\n }\n\n destroy(): void {\n this.events.removeAllListeners();\n this.msalInstance = null;\n this._state = { ...INITIAL_STATE };\n }\n\n // ── Auth Actions ──\n\n async handleRedirect(): Promise<AuthResponse | null> {\n this.assertInitialized();\n this.log(\"Handling redirect promise...\");\n\n try {\n const response = await this.msalInstance!.handleRedirectPromise();\n\n if (!response) {\n // No redirect response — check for existing sessions\n this.selectAccount(null);\n return null;\n }\n\n this.log(\"Redirect response received\", response);\n\n // Extract custom postback state\n const customState = extractCustomState(response.state);\n\n // Identify which policy was used via the acr/tfp claim\n const policy = this.provider.identifyPolicy(response);\n this.updateState({ activePolicy: policy });\n\n // Check if this was a forgot-password completion\n if (this.isForgotPasswordPolicy(policy)) {\n this.log(\"Forgot password flow completed\");\n this.events.emit(\"auth:forgotPassword\", undefined as never);\n return { ...response, customPostbackObject: customState ?? undefined } as AuthResponse;\n }\n\n // Normal sign-in flow\n this.updateState({ isLoading: true });\n this.selectAccount(policy);\n await this.acquireTokenAfterRedirect(response);\n\n const authResponse: AuthResponse = {\n ...response,\n customPostbackObject: customState ?? undefined,\n };\n\n this.events.emit(\"auth:signedIn\", authResponse);\n return authResponse;\n } catch (error) {\n return this.handleRedirectError(error);\n }\n }\n\n async signIn(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-in...\");\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:loading\", true);\n\n const request = this.buildLoginRequest(this.provider.getAuthority(), options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signInCityEmployee(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating city employee sign-in...\");\n\n if (!(this.provider instanceof B2CProvider)) {\n // For non-B2C providers, fall back to regular sign-in\n return this.signIn(options);\n }\n\n const authority = this.provider.getSignInOnlyAuthority();\n const request = this.buildLoginRequest(authority, options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signOut(options?: SignOutOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-out...\");\n\n const logoutRequest = {\n account: this._state.user ?? undefined,\n postLogoutRedirectUri: options?.postLogoutRedirectUri,\n authority: this.provider.getAuthority(),\n };\n\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:signedOut\", undefined as never);\n await this.msalInstance!.logoutRedirect(logoutRequest);\n }\n\n async forgotPassword(): Promise<void> {\n this.assertInitialized();\n\n if (!(this.provider instanceof B2CProvider)) {\n this.log(\"Forgot password is only supported for B2C providers\");\n return;\n }\n\n this.log(\"Initiating forgot password flow...\");\n const authority = this.provider.getResetPasswordAuthority();\n await this.msalInstance!.loginRedirect({ scopes: [], authority });\n }\n\n async acquireToken(options?: TokenOptions): Promise<string | null> {\n this.assertInitialized();\n this.log(\"Acquiring token...\");\n\n const account = this._state.user;\n if (!account) {\n this.log(\"No account found, cannot acquire token\");\n return null;\n }\n\n const scopes = options?.scopes ?? this.provider.getApiScopes();\n const tokenRequest = {\n scopes,\n forceRefresh: options?.forceRefresh ?? false,\n account,\n authority: this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority(),\n };\n\n try {\n const response = await this.msalInstance!.acquireTokenSilent(tokenRequest);\n\n if (!response.accessToken) {\n throw new InteractionRequiredAuthError(\"empty_token\");\n }\n\n this.log(\"Token acquired silently\");\n this.updateState({ token: response.accessToken, error: null });\n this.events.emit(\"auth:tokenAcquired\", response.accessToken);\n return response.accessToken;\n } catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n this.log(\"Silent token acquisition failed, falling back to redirect\");\n try {\n await this.msalInstance!.acquireTokenRedirect(tokenRequest);\n return null; // Page will redirect\n } catch (redirectError) {\n this.handleError(redirectError);\n return null;\n }\n }\n this.handleError(error);\n return null;\n }\n }\n\n // ── Internal Helpers ──\n\n private selectAccount(policy: string | null): void {\n const accounts = this.msalInstance!.getAllAccounts();\n\n if (accounts.length === 0) {\n this.updateState({ isAuthenticated: false, user: null });\n return;\n }\n\n let selected: AccountInfo | null = null;\n\n if (accounts.length === 1) {\n selected = accounts[0];\n } else if (policy) {\n // Filter accounts by policy and authority domain\n const filtered = accounts.filter(account => {\n const claims = account.idTokenClaims as Record<string, unknown> | undefined;\n const iss = (claims?.iss as string) ?? \"\";\n const knownAuthorities = this.provider.getKnownAuthorities();\n const matchesAuthority = knownAuthorities.some(auth => iss.toUpperCase().includes(auth.toUpperCase()));\n const matchesPolicy = account.homeAccountId.toUpperCase().includes(policy.toUpperCase());\n return matchesAuthority && matchesPolicy;\n });\n\n if (filtered.length >= 1) {\n selected = filtered[0];\n }\n }\n\n if (!selected && accounts.length > 0) {\n selected = accounts[0];\n }\n\n if (selected) {\n this.log(\"Account selected\", selected.username);\n this.updateState({ isAuthenticated: true, user: selected });\n }\n }\n\n private async acquireTokenAfterRedirect(response: AuthenticationResult): Promise<void> {\n // Set the account first so acquireToken can use it\n const account =\n this.msalInstance!.getAccountByHomeId(response.account?.homeAccountId ?? \"\") ?? response.account ?? null;\n\n if (account) {\n this.updateState({ isAuthenticated: true, user: account });\n }\n\n await this.acquireToken();\n this.updateState({ isLoading: false });\n }\n\n private buildLoginRequest(authority: string, options?: SignInOptions) {\n const scopes = options?.scopes ?? this.provider.getDefaultScopes();\n return {\n scopes,\n authority,\n state: this.encodedState ? `${this.encodedState}` : undefined,\n loginHint: options?.loginHint,\n domainHint: options?.domainHint,\n };\n }\n\n private isForgotPasswordPolicy(policy: string | null): boolean {\n if (!policy) return false;\n if (!(this.provider instanceof B2CProvider)) return false;\n return policy.toUpperCase() === this.provider.getPolicies().resetPassword.toUpperCase();\n }\n\n private handleRedirectError(error: unknown): null {\n const err = error as { errorMessage?: string; errorCode?: string };\n\n // Detect B2C forgot-password error code\n if (err.errorMessage?.includes(MSAL_ERROR_CODES.FORGOT_PASSWORD)) {\n this.log(\"Forgot password error detected, redirecting...\");\n this.forgotPassword();\n return null;\n }\n\n this.handleError(error);\n this.updateState({ isLoading: false });\n return null;\n }\n\n private handleError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"Error:\", err.message);\n this.updateState({ error: err });\n this.events.emit(\"auth:error\", err);\n }\n\n private updateState(partial: Partial<SSOClientState>): void {\n this._state = { ...this._state, ...partial };\n this.events.emit(\"auth:stateChanged\", this._state);\n }\n\n private assertInitialized(): void {\n if (!this.msalInstance) {\n throw new Error(\"SSOClient not initialized. Call initialize() first.\");\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) {\n console.log(\"[sso-core]\", ...args);\n }\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, CIAMProviderConfig } from \"../types\";\n\n/**\n * Entra External ID (CIAM) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class CIAMProvider implements IAuthProvider {\n readonly type = \"ciam\" as const;\n\n private readonly config: CIAMProviderConfig;\n\n constructor(config: CIAMProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://${this.config.tenantSubdomain}.ciamlogin.com/`;\n }\n\n getKnownAuthorities(): string[] {\n return [`${this.config.tenantSubdomain}.ciamlogin.com`];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // CIAM doesn't use client-side policy selection\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, EntraProviderConfig } from \"../types\";\n\n/**\n * Entra workforce (city employees) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class EntraProvider implements IAuthProvider {\n readonly type = \"entra\" as const;\n\n private readonly config: EntraProviderConfig;\n\n constructor(config: EntraProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://login.microsoftonline.com/${this.config.tenantId}`;\n }\n\n getKnownAuthorities(): string[] {\n return [\"login.microsoftonline.com\"];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // Entra workforce doesn't use B2C policies\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/events.ts","../src/constants.ts","../src/state-encoding.ts","../src/providers/b2c.ts","../src/client.ts","../src/providers/ciam.ts","../src/providers/entra.ts"],"sourcesContent":["import type { SSOEventMap, SSOEventName } from \"./types\";\n\ntype Listener<T> = (data: T) => void;\n\nexport class SSOEventEmitter {\n private listeners = new Map<SSOEventName, Set<Listener<unknown>>>();\n\n on<K extends SSOEventName>(event: K, listener: Listener<SSOEventMap[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener<unknown>);\n\n // Return unsubscribe function\n return () => {\n set.delete(listener as Listener<unknown>);\n };\n }\n\n emit<K extends SSOEventName>(event: K, data: SSOEventMap[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n for (const listener of set) {\n listener(data);\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","export const DEFAULT_POLICIES = {\n SIGN_UP_SIGN_IN: \"B2C_1A_SIGNUP_SIGNIN\",\n SIGN_IN_ONLY: \"B2C_1A_AD_SIGNIN_ONLY\",\n RESET_PASSWORD: \"B2C_1A_PASSWORDRESET\",\n} as const;\n\nexport const DEFAULT_SCOPES = {\n OPENID: \"openid\",\n PROFILE: \"profile\",\n OFFLINE_ACCESS: \"offline_access\",\n} as const;\n\nexport const CACHE_CONFIG = {\n LOCATION: \"sessionStorage\" as const,\n STORE_AUTH_STATE_IN_COOKIE: false,\n} as const;\n\nexport const MSAL_ERROR_CODES = {\n USER_CANCELLED: \"user_cancelled\",\n NO_CACHED_AUTHORITY: \"no_cached_authority_error\",\n INTERACTION_REQUIRED: \"interaction_required\",\n FORGOT_PASSWORD: \"AADB2C90118\",\n} as const;\n\nexport const STATE_SEPARATOR = \"|\";\n","import { STATE_SEPARATOR } from \"./constants\";\n\n/**\n * Encode a custom state object into a base64 string that can be\n * appended to the MSAL state parameter using a pipe separator.\n */\nexport function encodeState(stateObj: Record<string, unknown>): string {\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Decode a base64-encoded state string back into an object.\n */\nexport function decodeState(encoded: string): Record<string, unknown> | null {\n try {\n return JSON.parse(atob(encoded));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract custom postback data from the MSAL state string.\n * MSAL prepends its own GUID before the pipe separator.\n */\nexport function extractCustomState(msalState: string | undefined): Record<string, unknown> | null {\n if (!msalState) return null;\n\n const parts = msalState.split(STATE_SEPARATOR);\n if (parts.length < 2) return null;\n\n // Everything after the first pipe is our encoded state\n const customPart = parts.slice(1).join(STATE_SEPARATOR);\n return decodeState(customPart);\n}\n","import { LogLevel } from \"@azure/msal-browser\";\nimport type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, B2CProviderConfig, B2CPolicies } from \"../types\";\nimport { DEFAULT_POLICIES, DEFAULT_SCOPES, CACHE_CONFIG } from \"../constants\";\n\nexport class B2CProvider implements IAuthProvider {\n readonly type = \"b2c\" as const;\n\n private readonly clientId: string;\n private readonly b2cEnv: string;\n private readonly authorityDomain: string;\n private readonly redirectUri: string;\n private readonly postLogoutRedirectUri: string;\n private readonly policies: B2CPolicies;\n private readonly apiScopes: string[];\n private readonly cacheLocation: \"sessionStorage\" | \"localStorage\";\n\n constructor(config: B2CProviderConfig) {\n this.clientId = config.clientId;\n this.b2cEnv = config.b2cEnvironment;\n this.authorityDomain = config.authorityDomain;\n this.redirectUri = config.redirectUri;\n this.postLogoutRedirectUri = config.postLogoutRedirectUri ?? config.redirectUri;\n this.policies = {\n signUpSignIn: config.policies?.signUpSignIn ?? DEFAULT_POLICIES.SIGN_UP_SIGN_IN,\n signInOnly: config.policies?.signInOnly ?? DEFAULT_POLICIES.SIGN_IN_ONLY,\n resetPassword: config.policies?.resetPassword ?? DEFAULT_POLICIES.RESET_PASSWORD,\n };\n this.apiScopes = config.apiScopes ?? [];\n this.cacheLocation = config.cacheLocation ?? CACHE_CONFIG.LOCATION;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.clientId,\n authority: this.getAuthority(this.policies.signUpSignIn),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.redirectUri,\n postLogoutRedirectUri: this.postLogoutRedirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.cacheLocation,\n storeAuthStateInCookie: CACHE_CONFIG.STORE_AUTH_STATE_IN_COOKIE,\n },\n system: {\n loggerOptions: {\n logLevel: LogLevel.Warning,\n loggerCallback: (_level: LogLevel, message: string) => {\n console.warn(\"[sso-core/b2c]\", message);\n },\n },\n },\n };\n }\n\n getAuthority(policy?: string): string {\n const p = policy ?? this.policies.signUpSignIn;\n return `https://${this.authorityDomain}/${this.b2cEnv}.onmicrosoft.com/${p}`;\n }\n\n getKnownAuthorities(): string[] {\n return [this.authorityDomain];\n }\n\n identifyPolicy(response: AuthenticationResult): string | null {\n const claims = response.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n\n const acr = (claims.acr ?? claims.tfp) as string | undefined;\n return acr?.toUpperCase() ?? null;\n }\n\n getDefaultScopes(): string[] {\n return [DEFAULT_SCOPES.OPENID, DEFAULT_SCOPES.PROFILE];\n }\n\n getApiScopes(): string[] {\n return this.apiScopes;\n }\n\n /** Get the authority URL for the sign-in-only (city employee) policy */\n getSignInOnlyAuthority(): string {\n return this.getAuthority(this.policies.signInOnly);\n }\n\n /** Get the authority URL for the password reset policy */\n getResetPasswordAuthority(): string {\n return this.getAuthority(this.policies.resetPassword);\n }\n\n /** Get all configured policy names */\n getPolicies(): Readonly<B2CPolicies> {\n return this.policies;\n }\n}\n","import { PublicClientApplication, InteractionRequiredAuthError } from \"@azure/msal-browser\";\nimport type { AccountInfo, AuthenticationResult } from \"@azure/msal-browser\";\nimport { SSOEventEmitter } from \"./events\";\nimport { encodeState, extractCustomState } from \"./state-encoding\";\nimport { MSAL_ERROR_CODES } from \"./constants\";\nimport { B2CProvider } from \"./providers/b2c\";\nimport type {\n IAuthProvider,\n SSOClientConfig,\n SSOClientState,\n SignInOptions,\n SignOutOptions,\n TokenOptions,\n AuthResponse,\n} from \"./types\";\n\nconst INITIAL_STATE: SSOClientState = {\n isAuthenticated: false,\n isLoading: false,\n user: null,\n token: null,\n error: null,\n activePolicy: null,\n authReady: false,\n};\n\nexport class SSOClient {\n readonly events = new SSOEventEmitter();\n private readonly provider: IAuthProvider;\n private readonly debug: boolean;\n private readonly encodedState: string | null;\n\n private msalInstance: PublicClientApplication | null = null;\n private _state: SSOClientState = { ...INITIAL_STATE };\n\n constructor(config: SSOClientConfig) {\n this.provider = config.provider;\n this.debug = config.debug ?? false;\n this.encodedState = config.state ? encodeState(config.state) : null;\n }\n\n get state(): Readonly<SSOClientState> {\n return this._state;\n }\n\n // ── Lifecycle ──\n\n async initialize(): Promise<AuthResponse | null> {\n this.log(\"Initializing SSOClient...\");\n this.updateState({ isLoading: true });\n\n const msalConfig = this.provider.buildMsalConfig();\n this.msalInstance = new PublicClientApplication(msalConfig);\n\n await this.msalInstance.initialize();\n\n const result = await this.handleRedirect();\n\n this.updateState({ isLoading: false, authReady: true });\n return result;\n }\n\n destroy(): void {\n this.events.removeAllListeners();\n this.msalInstance = null;\n this._state = { ...INITIAL_STATE };\n }\n\n // ── Auth Actions ──\n\n async handleRedirect(): Promise<AuthResponse | null> {\n this.assertInitialized();\n this.log(\"Handling redirect promise...\");\n\n try {\n const response = await this.msalInstance!.handleRedirectPromise();\n\n if (!response) {\n // No redirect response — check for existing sessions\n this.selectAccount(null);\n return null;\n }\n\n this.log(\"Redirect response received\", response);\n\n // Extract custom postback state\n const customState = extractCustomState(response.state);\n\n // Identify which policy was used via the acr/tfp claim\n const policy = this.provider.identifyPolicy(response);\n this.updateState({ activePolicy: policy });\n\n // Check if this was a forgot-password completion\n if (this.isForgotPasswordPolicy(policy)) {\n this.log(\"Forgot password flow completed\");\n this.events.emit(\"auth:forgotPassword\", undefined as never);\n return { ...response, customPostbackObject: customState ?? undefined } as AuthResponse;\n }\n\n // Normal sign-in flow\n this.updateState({ isLoading: true });\n this.selectAccount(policy);\n await this.acquireTokenAfterRedirect(response);\n\n const authResponse: AuthResponse = {\n ...response,\n customPostbackObject: customState ?? undefined,\n };\n\n this.events.emit(\"auth:signedIn\", authResponse);\n return authResponse;\n } catch (error) {\n return this.handleRedirectError(error);\n }\n }\n\n async signIn(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-in...\");\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:loading\", true);\n\n const request = this.buildLoginRequest(this.provider.getAuthority(), options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signInCityEmployee(options?: SignInOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating city employee sign-in...\");\n\n if (!(this.provider instanceof B2CProvider)) {\n // For non-B2C providers, fall back to regular sign-in\n return this.signIn(options);\n }\n\n const authority = this.provider.getSignInOnlyAuthority();\n const request = this.buildLoginRequest(authority, options);\n await this.msalInstance!.loginRedirect(request);\n }\n\n async signOut(options?: SignOutOptions): Promise<void> {\n this.assertInitialized();\n this.log(\"Initiating sign-out...\");\n\n const authority = this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority();\n\n // logoutRedirect() performs iframe-based silent logout for federated\n // accounts (e.g. Entra ID). Microsoft's endpoints block iframes with\n // X-Frame-Options: deny, causing visible console errors and broken flows.\n // We clear the local MSAL cache and navigate directly to the B2C\n // end_session endpoint instead, which avoids any iframe usage.\n await this.msalInstance!.clearCache();\n\n const msalAuthConfig = this.msalInstance!.getConfiguration().auth;\n const postLogoutRedirectUri =\n options?.postLogoutRedirectUri ??\n (msalAuthConfig.postLogoutRedirectUri as string | undefined) ??\n window.location.origin;\n\n this.updateState({ isLoading: true });\n this.events.emit(\"auth:signedOut\", undefined as never);\n\n window.location.href = `${authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`;\n }\n\n async forgotPassword(): Promise<void> {\n this.assertInitialized();\n\n if (!(this.provider instanceof B2CProvider)) {\n this.log(\"Forgot password is only supported for B2C providers\");\n return;\n }\n\n this.log(\"Initiating forgot password flow...\");\n const authority = this.provider.getResetPasswordAuthority();\n await this.msalInstance!.loginRedirect({ scopes: [], authority });\n }\n\n async acquireToken(options?: TokenOptions): Promise<string | null> {\n this.assertInitialized();\n this.log(\"Acquiring token...\");\n\n const account = this._state.user;\n if (!account) {\n this.log(\"No account found, cannot acquire token\");\n return null;\n }\n\n const scopes = options?.scopes ?? this.provider.getApiScopes();\n const tokenRequest = {\n scopes,\n forceRefresh: options?.forceRefresh ?? false,\n account,\n authority: this._state.activePolicy\n ? this.provider.getAuthority(this._state.activePolicy)\n : this.provider.getAuthority(),\n };\n\n try {\n const response = await this.msalInstance!.acquireTokenSilent(tokenRequest);\n\n if (!response.accessToken) {\n throw new InteractionRequiredAuthError(\"empty_token\");\n }\n\n this.log(\"Token acquired silently\");\n this.updateState({ token: response.accessToken, error: null });\n this.events.emit(\"auth:tokenAcquired\", response.accessToken);\n return response.accessToken;\n } catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n this.log(\"Silent token acquisition failed, falling back to redirect\");\n try {\n await this.msalInstance!.acquireTokenRedirect(tokenRequest);\n return null; // Page will redirect\n } catch (redirectError) {\n this.handleError(redirectError);\n return null;\n }\n }\n this.handleError(error);\n return null;\n }\n }\n\n // ── Internal Helpers ──\n\n private selectAccount(policy: string | null): void {\n const accounts = this.msalInstance!.getAllAccounts();\n\n if (accounts.length === 0) {\n this.updateState({ isAuthenticated: false, user: null });\n return;\n }\n\n let selected: AccountInfo | null = null;\n\n if (accounts.length === 1) {\n selected = accounts[0];\n } else if (policy) {\n // Filter accounts by policy and authority domain\n const filtered = accounts.filter(account => {\n const claims = account.idTokenClaims as Record<string, unknown> | undefined;\n const iss = (claims?.iss as string) ?? \"\";\n const knownAuthorities = this.provider.getKnownAuthorities();\n const matchesAuthority = knownAuthorities.some(auth => iss.toUpperCase().includes(auth.toUpperCase()));\n const matchesPolicy = account.homeAccountId.toUpperCase().includes(policy.toUpperCase());\n return matchesAuthority && matchesPolicy;\n });\n\n if (filtered.length >= 1) {\n selected = filtered[0];\n }\n }\n\n if (!selected && accounts.length > 0) {\n selected = accounts[0];\n }\n\n if (selected) {\n this.log(\"Account selected\", selected.username);\n this.updateState({ isAuthenticated: true, user: selected });\n }\n }\n\n private async acquireTokenAfterRedirect(response: AuthenticationResult): Promise<void> {\n // Set the account first so acquireToken can use it\n const account =\n this.msalInstance!.getAccountByHomeId(response.account?.homeAccountId ?? \"\") ?? response.account ?? null;\n\n if (account) {\n this.updateState({ isAuthenticated: true, user: account });\n }\n\n await this.acquireToken();\n this.updateState({ isLoading: false });\n }\n\n private buildLoginRequest(authority: string, options?: SignInOptions) {\n const scopes = options?.scopes ?? this.provider.getDefaultScopes();\n return {\n scopes,\n authority,\n state: this.encodedState ? `${this.encodedState}` : undefined,\n loginHint: options?.loginHint,\n domainHint: options?.domainHint,\n };\n }\n\n private isForgotPasswordPolicy(policy: string | null): boolean {\n if (!policy) return false;\n if (!(this.provider instanceof B2CProvider)) return false;\n return policy.toUpperCase() === this.provider.getPolicies().resetPassword.toUpperCase();\n }\n\n private handleRedirectError(error: unknown): null {\n const err = error as { errorMessage?: string; errorCode?: string };\n\n // Detect B2C forgot-password error code\n if (err.errorMessage?.includes(MSAL_ERROR_CODES.FORGOT_PASSWORD)) {\n this.log(\"Forgot password error detected, redirecting...\");\n this.forgotPassword();\n return null;\n }\n\n this.handleError(error);\n this.updateState({ isLoading: false });\n return null;\n }\n\n private handleError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"Error:\", err.message);\n this.updateState({ error: err });\n this.events.emit(\"auth:error\", err);\n }\n\n private updateState(partial: Partial<SSOClientState>): void {\n this._state = { ...this._state, ...partial };\n this.events.emit(\"auth:stateChanged\", this._state);\n }\n\n private assertInitialized(): void {\n if (!this.msalInstance) {\n throw new Error(\"SSOClient not initialized. Call initialize() first.\");\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) {\n console.log(\"[sso-core]\", ...args);\n }\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, CIAMProviderConfig } from \"../types\";\n\n/**\n * Entra External ID (CIAM) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class CIAMProvider implements IAuthProvider {\n readonly type = \"ciam\" as const;\n\n private readonly config: CIAMProviderConfig;\n\n constructor(config: CIAMProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://${this.config.tenantSubdomain}.ciamlogin.com/`;\n }\n\n getKnownAuthorities(): string[] {\n return [`${this.config.tenantSubdomain}.ciamlogin.com`];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // CIAM doesn't use client-side policy selection\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n","import type { Configuration, AuthenticationResult } from \"@azure/msal-browser\";\nimport type { IAuthProvider, EntraProviderConfig } from \"../types\";\n\n/**\n * Entra workforce (city employees) provider — future implementation.\n * Placeholder scaffolding to validate the provider interface.\n */\nexport class EntraProvider implements IAuthProvider {\n readonly type = \"entra\" as const;\n\n private readonly config: EntraProviderConfig;\n\n constructor(config: EntraProviderConfig) {\n this.config = config;\n }\n\n buildMsalConfig(): Configuration {\n return {\n auth: {\n clientId: this.config.clientId,\n authority: this.getAuthority(),\n knownAuthorities: this.getKnownAuthorities(),\n redirectUri: this.config.redirectUri,\n postLogoutRedirectUri: this.config.postLogoutRedirectUri ?? this.config.redirectUri,\n navigateToLoginRequestUrl: false,\n },\n cache: {\n cacheLocation: this.config.cacheLocation ?? \"sessionStorage\",\n },\n };\n }\n\n getAuthority(): string {\n return `https://login.microsoftonline.com/${this.config.tenantId}`;\n }\n\n getKnownAuthorities(): string[] {\n return [\"login.microsoftonline.com\"];\n }\n\n identifyPolicy(_response: AuthenticationResult): string | null {\n // Entra workforce doesn't use B2C policies\n return null;\n }\n\n getDefaultScopes(): string[] {\n return [\"openid\", \"profile\"];\n }\n\n getApiScopes(): string[] {\n return this.config.scopes ?? [];\n }\n}\n"],"names":[],"mappings":"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"}
|