@memberjunction/ng-auth-services 5.42.0 → 5.44.0

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/README.md CHANGED
@@ -1,33 +1,37 @@
1
1
  # @memberjunction/ng-auth-services
2
2
 
3
- Angular authentication services for MemberJunction Explorer applications. Provides a unified abstraction layer supporting multiple OAuth providers (Auth0, Microsoft MSAL, Okta) through a consistent, type-safe API.
3
+ Angular authentication services for MemberJunction Explorer applications. Provides a unified abstraction layer supporting multiple OAuth providers (Auth0, Microsoft MSAL, Okta, Cognito, WorkOS) through a consistent, type-safe API.
4
4
 
5
5
  ## Overview
6
6
 
7
- This package implements a provider-agnostic authentication system using the Strategy pattern. A single abstract base class (`MJAuthBase`) defines the contract, while concrete providers handle Auth0, MSAL (Azure AD), and Okta specifics. Application code interacts only with the standardized interface, making provider switches a configuration change rather than a code change.
7
+ This package implements a provider-agnostic authentication system using the Strategy pattern. A single abstract base class (`MJAuthBase`) defines the contract, while concrete providers handle Auth0, MSAL (Azure AD), Okta, Cognito, and WorkOS (AuthKit) specifics. Application code interacts only with the standardized interface, making provider switches a configuration change rather than a code change.
8
8
 
9
9
  ```mermaid
10
10
  graph TD
11
11
  A["MJAuthBase\n(Abstract Service)"] --> B["MJAuth0Provider"]
12
12
  A --> C["MJMSALProvider"]
13
13
  A --> D["MJOktaProvider"]
14
+ A --> H["MJWorkOSProvider"]
14
15
  B --> E["@auth0/auth0-angular"]
15
16
  C --> F["@azure/msal-angular"]
16
17
  D --> G["@okta/okta-auth-js"]
18
+ H --> I["@workos-inc/authkit-js"]
17
19
 
18
20
  style A fill:#7c5295,stroke:#563a6b,color:#fff
19
21
  style B fill:#2d6a9f,stroke:#1a4971,color:#fff
20
22
  style C fill:#2d6a9f,stroke:#1a4971,color:#fff
21
23
  style D fill:#2d6a9f,stroke:#1a4971,color:#fff
24
+ style H fill:#2d6a9f,stroke:#1a4971,color:#fff
22
25
  style E fill:#2d8659,stroke:#1a5c3a,color:#fff
23
26
  style F fill:#2d8659,stroke:#1a5c3a,color:#fff
24
27
  style G fill:#2d8659,stroke:#1a5c3a,color:#fff
28
+ style I fill:#2d8659,stroke:#1a5c3a,color:#fff
25
29
  ```
26
30
 
27
31
  ## Features
28
32
 
29
33
  - **Unified Authentication Interface**: Standardized API across all providers via `IAngularAuthProvider`
30
- - **Multiple Provider Support**: Auth0, Microsoft MSAL (Azure AD), and Okta
34
+ - **Multiple Provider Support**: Auth0, Microsoft MSAL (Azure AD), Okta, Cognito, and WorkOS (AuthKit)
31
35
  - **Standardized Types (v3.0.0)**: `StandardUserInfo`, `StandardAuthToken`, `StandardAuthError` eliminate leaky abstractions
32
36
  - **Semantic Error Classification**: `AuthErrorType` enum replaces provider-specific error checking
33
37
  - **Token Management**: ID token retrieval, token info, and automatic refresh
@@ -49,6 +53,7 @@ npm install @memberjunction/ng-auth-services
49
53
  | `@auth0/auth0-angular` | Auth0 provider (peer) |
50
54
  | `@azure/msal-angular`, `@azure/msal-browser` | MSAL provider (peer) |
51
55
  | `@okta/okta-auth-js` | Okta provider (peer) |
56
+ | `@workos-inc/authkit-js` | WorkOS AuthKit provider (peer) |
52
57
  | `@memberjunction/core` | Core MJ utilities |
53
58
  | `rxjs` | Reactive state management |
54
59
 
@@ -77,8 +82,19 @@ export const environment = {
77
82
  OKTA_ISSUER: 'https://your-org.okta.com/oauth2/default',
78
83
  OKTA_CLIENTID: 'your-okta-client-id',
79
84
  };
85
+
86
+ // WorkOS (AuthKit)
87
+ export const environment = {
88
+ AUTH_TYPE: 'workos',
89
+ WORKOS_CLIENTID: 'client_01H...',
90
+ // optional: WORKOS_REDIRECT_URI, WORKOS_API_HOSTNAME, WORKOS_DEV_MODE
91
+ };
80
92
  ```
81
93
 
94
+ > **WorkOS needs server-side setup too** — a JWT Template to add the user's `email` to the access
95
+ > token (WorkOS omits it by default, and MJ resolves users by email) and matching the `aud` claim.
96
+ > See the end-to-end **[WorkOS Integration Guide](../../../AuthProviders/WORKOS.md)**.
97
+
82
98
  ### Module Setup
83
99
 
84
100
  ```typescript
@@ -0,0 +1,118 @@
1
+ import { OnDestroy } from '@angular/core';
2
+ import { MJAuthBase } from '../mjexplorer-auth-base.service';
3
+ import { StandardUserInfo, StandardAuthToken, StandardAuthError, TokenRefreshResult } from '../auth-types';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Resolved configuration for the WorkOS AuthKit client.
7
+ * Provided via the `'workosConfig'` injection token (see `angularProviderFactory`).
8
+ */
9
+ export interface WorkOSAuthConfig {
10
+ /** WorkOS AuthKit client ID (e.g. `client_01H...`). Required. */
11
+ clientId: string;
12
+ /** OAuth redirect URI registered in the WorkOS dashboard. Defaults to `window.location.origin`. */
13
+ redirectUri?: string;
14
+ /** Override the WorkOS API hostname (rarely needed; for proxies / custom domains). */
15
+ apiHostname?: string;
16
+ /** Enables AuthKit dev mode (localStorage session, no third-party cookies). */
17
+ devMode?: boolean;
18
+ }
19
+ /**
20
+ * WorkOS (AuthKit) authentication provider — browser side.
21
+ *
22
+ * Wraps the vanilla-JS `@workos-inc/authkit-js` SDK behind {@link MJAuthBase} so the rest of
23
+ * MemberJunction never sees WorkOS-specific details. AuthKit issues a JWT **access token**
24
+ * (returned by `getAccessToken()`) that MJ sends to the GraphQL API as a Bearer token; the
25
+ * server validates it via `@memberjunction/auth-providers`' `WorkOSProvider`.
26
+ *
27
+ * Unlike the Auth0/MSAL/Okta providers, AuthKit is not an Angular library — there is no module
28
+ * to import. This provider creates the client itself in {@link initialize} using config supplied
29
+ * through the `'workosConfig'` injection token.
30
+ *
31
+ * > **Email note:** `getUser()` always returns the user's email for display here, but the
32
+ * > **access token** only carries `email` if a WorkOS JWT Template adds it. MJ resolves users by
33
+ * > email server-side, so configuring that template is required. See `WORKOS.md` in
34
+ * > `@memberjunction/auth-providers`.
35
+ */
36
+ export declare class MJWorkOSProvider extends MJAuthBase implements OnDestroy {
37
+ private workosConfig;
38
+ static readonly PROVIDER_TYPE = "workos";
39
+ readonly type = "workos";
40
+ private client;
41
+ private initPromise;
42
+ /**
43
+ * Factory function to provide the Angular dependencies required by WorkOS.
44
+ * Stored as a static property so the factory can read it without instantiation.
45
+ */
46
+ static angularProviderFactory: (environment: Record<string, unknown>) => {
47
+ provide: string;
48
+ useValue: {
49
+ clientId: string;
50
+ redirectUri: string;
51
+ apiHostname: string | undefined;
52
+ devMode: boolean;
53
+ };
54
+ }[];
55
+ constructor(workosConfig: WorkOSAuthConfig);
56
+ /**
57
+ * Initialize the AuthKit client. `createClient()` constructs the client AND processes any
58
+ * pending OAuth redirect (it reads the `code` from the URL and exchanges it), so once it
59
+ * resolves the session — if any — is already established and `getUser()` is populated.
60
+ *
61
+ * Idempotent and concurrency-safe: parallel callers share a single in-flight promise.
62
+ */
63
+ initialize(): Promise<void>;
64
+ private createAndSyncClient;
65
+ /**
66
+ * Pushes the current AuthKit session into MJ's reactive streams.
67
+ */
68
+ private syncSessionState;
69
+ protected loginInternal(options?: Record<string, unknown>): Promise<void>;
70
+ protected logoutInternal(): Promise<void>;
71
+ handleCallback(): Promise<void>;
72
+ ngOnDestroy(): void;
73
+ /**
74
+ * Extract the access token (a JWT) from AuthKit. `getAccessToken()` returns a valid token,
75
+ * silently refreshing first if the current one is near expiry. Throws when there is no
76
+ * session — we translate that to `null` so callers can treat it as "not authenticated".
77
+ */
78
+ protected extractIdTokenInternal(): Promise<string | null>;
79
+ /**
80
+ * Extract complete token info. The expiry comes from the JWT's `exp` claim, decoded with the
81
+ * SDK's `getClaims()` helper.
82
+ */
83
+ protected extractTokenInfoInternal(): Promise<StandardAuthToken | null>;
84
+ /**
85
+ * Extract user info from AuthKit's in-memory user (always populated when authenticated,
86
+ * regardless of which claims the access token carries).
87
+ */
88
+ protected extractUserInfoInternal(): Promise<StandardUserInfo | null>;
89
+ /**
90
+ * Force a token refresh through AuthKit's refresh-token rotation.
91
+ */
92
+ protected refreshTokenInternal(): Promise<TokenRefreshResult>;
93
+ /**
94
+ * Map WorkOS / AuthKit SDK errors to semantic `AuthErrorType` values.
95
+ */
96
+ protected classifyErrorInternal(error: unknown): StandardAuthError;
97
+ /**
98
+ * WorkOS includes the profile picture URL on the user object — no extra API call needed.
99
+ */
100
+ protected getProfilePictureUrlInternal(): Promise<string | null>;
101
+ /**
102
+ * No-op. WorkOS uses rotating refresh tokens, so it doesn't need interactive
103
+ * re-authentication when an access token expires; if refresh fails the base class surfaces
104
+ * the error and the user re-logs in manually.
105
+ */
106
+ protected handleSessionExpiryInternal(): Promise<void>;
107
+ private ensureInitialized;
108
+ /**
109
+ * Map a WorkOS `User` to the framework-standard `StandardUserInfo`.
110
+ * WorkOS exposes `firstName`/`lastName` as `string | null`; we normalize null → undefined.
111
+ */
112
+ private mapWorkOSUserToStandard;
113
+ getRequiredConfig(): string[];
114
+ validateConfig(config: Record<string, unknown>): boolean;
115
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJWorkOSProvider, never>;
116
+ static ɵprov: i0.ɵɵInjectableDeclaration<MJWorkOSProvider>;
117
+ }
118
+ //# sourceMappingURL=mjexplorer-workos-provider.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mjexplorer-workos-provider.service.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/mjexplorer-workos-provider.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAE,MAAM,eAAe,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAU7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EAEjB,kBAAkB,EACnB,MAAM,eAAe,CAAC;;AAQvB;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAIa,gBAAiB,SAAQ,UAAW,YAAW,SAAS;IAuB/B,OAAO,CAAC,YAAY;IAtBxD,MAAM,CAAC,QAAQ,CAAC,aAAa,YAAY;IACzC,QAAQ,CAAC,IAAI,YAAkC;IAE/C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,WAAW,CAA8B;IAEjD;;;OAGG;IACH,MAAM,CAAC,sBAAsB,GAAI,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;sBAInB,MAAM;;yBAEC,MAAM,GAAG,SAAS;;;QAIzE;gBAE0C,YAAY,EAAE,gBAAgB;IAY1E;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBnB,mBAAmB;IAgBjC;;OAEG;IACH,OAAO,CAAC,gBAAgB;cAWR,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;cAO/D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBzC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrC,WAAW,IAAI,IAAI;IAUnB;;;;OAIG;cACa,sBAAsB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAehE;;;OAGG;cACa,wBAAwB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAqB7E;;;OAGG;cACa,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAK3E;;OAEG;cACa,oBAAoB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAoCnE;;OAEG;IACH,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB;IAuClE;;OAEG;cACa,4BAA4B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAItE;;;;OAIG;cACa,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;YAQ9C,iBAAiB;IAM/B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAkBtB,iBAAiB,IAAI,MAAM,EAAE;IAI7B,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;yCA5TtD,gBAAgB;6CAAhB,gBAAgB;CAiU5B"}
@@ -0,0 +1,344 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var MJWorkOSProvider_1;
8
+ import { Injectable, Inject } from '@angular/core';
9
+ import { RegisterClass } from '@memberjunction/global';
10
+ import { MJAuthBase } from '../mjexplorer-auth-base.service';
11
+ import { createClient, getClaims, LoginRequiredError, NoSessionError, RefreshTimeoutError } from '@workos-inc/authkit-js';
12
+ import { AuthErrorType } from '../auth-types';
13
+ import * as i0 from "@angular/core";
14
+ /**
15
+ * WorkOS (AuthKit) authentication provider — browser side.
16
+ *
17
+ * Wraps the vanilla-JS `@workos-inc/authkit-js` SDK behind {@link MJAuthBase} so the rest of
18
+ * MemberJunction never sees WorkOS-specific details. AuthKit issues a JWT **access token**
19
+ * (returned by `getAccessToken()`) that MJ sends to the GraphQL API as a Bearer token; the
20
+ * server validates it via `@memberjunction/auth-providers`' `WorkOSProvider`.
21
+ *
22
+ * Unlike the Auth0/MSAL/Okta providers, AuthKit is not an Angular library — there is no module
23
+ * to import. This provider creates the client itself in {@link initialize} using config supplied
24
+ * through the `'workosConfig'` injection token.
25
+ *
26
+ * > **Email note:** `getUser()` always returns the user's email for display here, but the
27
+ * > **access token** only carries `email` if a WorkOS JWT Template adds it. MJ resolves users by
28
+ * > email server-side, so configuring that template is required. See `WORKOS.md` in
29
+ * > `@memberjunction/auth-providers`.
30
+ */
31
+ let MJWorkOSProvider = class MJWorkOSProvider extends MJAuthBase {
32
+ static { MJWorkOSProvider_1 = this; }
33
+ workosConfig;
34
+ static PROVIDER_TYPE = 'workos';
35
+ type = MJWorkOSProvider_1.PROVIDER_TYPE;
36
+ client = null;
37
+ initPromise = null;
38
+ /**
39
+ * Factory function to provide the Angular dependencies required by WorkOS.
40
+ * Stored as a static property so the factory can read it without instantiation.
41
+ */
42
+ static angularProviderFactory = (environment) => [
43
+ {
44
+ provide: 'workosConfig',
45
+ useValue: {
46
+ clientId: environment['WORKOS_CLIENTID'],
47
+ redirectUri: environment['WORKOS_REDIRECT_URI'] || window.location.origin,
48
+ apiHostname: environment['WORKOS_API_HOSTNAME'],
49
+ devMode: environment['WORKOS_DEV_MODE'] ?? false
50
+ }
51
+ }
52
+ ];
53
+ constructor(workosConfig) {
54
+ const config = {
55
+ name: MJWorkOSProvider_1.PROVIDER_TYPE,
56
+ type: MJWorkOSProvider_1.PROVIDER_TYPE
57
+ };
58
+ super(config);
59
+ this.workosConfig = workosConfig;
60
+ }
61
+ // ============================================================================
62
+ // LIFECYCLE METHODS
63
+ // ============================================================================
64
+ /**
65
+ * Initialize the AuthKit client. `createClient()` constructs the client AND processes any
66
+ * pending OAuth redirect (it reads the `code` from the URL and exchanges it), so once it
67
+ * resolves the session — if any — is already established and `getUser()` is populated.
68
+ *
69
+ * Idempotent and concurrency-safe: parallel callers share a single in-flight promise.
70
+ */
71
+ async initialize() {
72
+ if (this.client) {
73
+ return;
74
+ }
75
+ if (this.initPromise) {
76
+ return this.initPromise;
77
+ }
78
+ this.initPromise = this.createAndSyncClient();
79
+ try {
80
+ await this.initPromise;
81
+ }
82
+ finally {
83
+ this.initPromise = null;
84
+ }
85
+ }
86
+ async createAndSyncClient() {
87
+ this.client = await createClient(this.workosConfig.clientId, {
88
+ redirectUri: this.workosConfig.redirectUri,
89
+ apiHostname: this.workosConfig.apiHostname,
90
+ devMode: this.workosConfig.devMode,
91
+ // Keep MJ's reactive auth state in lockstep with AuthKit's silent refresh cycle.
92
+ onRefresh: () => this.syncSessionState(),
93
+ onRefreshFailure: () => {
94
+ this.updateAuthState(false);
95
+ this.updateUserInfo(null);
96
+ }
97
+ });
98
+ this.syncSessionState();
99
+ }
100
+ /**
101
+ * Pushes the current AuthKit session into MJ's reactive streams.
102
+ */
103
+ syncSessionState() {
104
+ const user = this.client?.getUser() ?? null;
105
+ if (user) {
106
+ this.updateAuthState(true);
107
+ this.updateUserInfo(this.mapWorkOSUserToStandard(user));
108
+ }
109
+ else {
110
+ this.updateAuthState(false);
111
+ this.updateUserInfo(null);
112
+ }
113
+ }
114
+ async loginInternal(options) {
115
+ await this.ensureInitialized();
116
+ // signIn redirects the browser to the WorkOS hosted login page. Any caller-supplied
117
+ // options (loginHint, organizationId, state, …) are passed straight through.
118
+ await this.client.signIn(options);
119
+ }
120
+ async logoutInternal() {
121
+ // signOut clears the AuthKit session and redirects to the app's configured Logout URI.
122
+ // If there is no active session it throws NoSessionError — provide returnTo so the user is
123
+ // still returned to the app rather than left on a dead page.
124
+ this.updateAuthState(false);
125
+ this.updateUserInfo(null);
126
+ try {
127
+ this.client?.signOut({ returnTo: this.workosConfig.redirectUri || window.location.origin });
128
+ }
129
+ catch (error) {
130
+ // NoSessionError (no active session) — just hard-redirect home.
131
+ if (error instanceof NoSessionError) {
132
+ window.location.href = window.location.origin;
133
+ }
134
+ else {
135
+ throw error;
136
+ }
137
+ }
138
+ }
139
+ async handleCallback() {
140
+ // AuthKit's createClient() processes the redirect internally during initialize().
141
+ // This method exists for interface compatibility (mirrors the Auth0 provider).
142
+ await this.ensureInitialized();
143
+ }
144
+ ngOnDestroy() {
145
+ // Release the AuthKit refresh timer / listeners.
146
+ this.client?.dispose();
147
+ this.client = null;
148
+ }
149
+ // ============================================================================
150
+ // ABSTRACT METHOD IMPLEMENTATIONS
151
+ // ============================================================================
152
+ /**
153
+ * Extract the access token (a JWT) from AuthKit. `getAccessToken()` returns a valid token,
154
+ * silently refreshing first if the current one is near expiry. Throws when there is no
155
+ * session — we translate that to `null` so callers can treat it as "not authenticated".
156
+ */
157
+ async extractIdTokenInternal() {
158
+ if (!this.client) {
159
+ return null;
160
+ }
161
+ try {
162
+ return await this.client.getAccessToken();
163
+ }
164
+ catch (error) {
165
+ if (error instanceof LoginRequiredError) {
166
+ return null;
167
+ }
168
+ console.error('[WorkOS] Error extracting access token:', error);
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * Extract complete token info. The expiry comes from the JWT's `exp` claim, decoded with the
174
+ * SDK's `getClaims()` helper.
175
+ */
176
+ async extractTokenInfoInternal() {
177
+ const accessToken = await this.extractIdTokenInternal();
178
+ if (!accessToken) {
179
+ return null;
180
+ }
181
+ let expiresAt = 0;
182
+ try {
183
+ const claims = getClaims(accessToken);
184
+ expiresAt = claims.exp ? claims.exp * 1000 : 0; // exp is seconds → milliseconds
185
+ }
186
+ catch (error) {
187
+ console.warn('[WorkOS] Could not decode access token claims:', error);
188
+ }
189
+ return {
190
+ idToken: accessToken,
191
+ accessToken,
192
+ expiresAt
193
+ };
194
+ }
195
+ /**
196
+ * Extract user info from AuthKit's in-memory user (always populated when authenticated,
197
+ * regardless of which claims the access token carries).
198
+ */
199
+ async extractUserInfoInternal() {
200
+ const user = this.client?.getUser() ?? null;
201
+ return user ? this.mapWorkOSUserToStandard(user) : null;
202
+ }
203
+ /**
204
+ * Force a token refresh through AuthKit's refresh-token rotation.
205
+ */
206
+ async refreshTokenInternal() {
207
+ if (!this.client) {
208
+ return {
209
+ success: false,
210
+ error: {
211
+ type: AuthErrorType.NO_ACTIVE_SESSION,
212
+ message: 'WorkOS client not initialized',
213
+ userMessage: 'You need to log in to continue.'
214
+ }
215
+ };
216
+ }
217
+ try {
218
+ const accessToken = await this.client.getAccessToken({ forceRefresh: true });
219
+ this.syncSessionState();
220
+ let expiresAt = 0;
221
+ try {
222
+ const claims = getClaims(accessToken);
223
+ expiresAt = claims.exp ? claims.exp * 1000 : 0;
224
+ }
225
+ catch {
226
+ // exp decode is best-effort; a missing value just means the consumer can't pre-empt expiry.
227
+ }
228
+ return {
229
+ success: true,
230
+ token: { idToken: accessToken, accessToken, expiresAt }
231
+ };
232
+ }
233
+ catch (error) {
234
+ return {
235
+ success: false,
236
+ error: this.classifyErrorInternal(error)
237
+ };
238
+ }
239
+ }
240
+ /**
241
+ * Map WorkOS / AuthKit SDK errors to semantic `AuthErrorType` values.
242
+ */
243
+ classifyErrorInternal(error) {
244
+ if (error instanceof LoginRequiredError || error instanceof NoSessionError) {
245
+ return {
246
+ type: AuthErrorType.NO_ACTIVE_SESSION,
247
+ message: error.message,
248
+ originalError: error,
249
+ userMessage: 'You need to log in to continue.'
250
+ };
251
+ }
252
+ // RefreshTimeoutError is exported; its parent `RefreshError` is not, so we also match by
253
+ // constructor name to catch any non-timeout refresh failure.
254
+ if (error instanceof RefreshTimeoutError || error?.name === 'RefreshError') {
255
+ return {
256
+ type: AuthErrorType.TOKEN_EXPIRED,
257
+ message: error.message,
258
+ originalError: error,
259
+ userMessage: 'Your session has expired. Please log in again.'
260
+ };
261
+ }
262
+ const message = error?.message || 'Unknown error';
263
+ if (message.includes('network') || message.includes('fetch') || message.includes('Failed to fetch')) {
264
+ return {
265
+ type: AuthErrorType.NETWORK_ERROR,
266
+ message,
267
+ originalError: error,
268
+ userMessage: 'Network error. Please check your connection and try again.'
269
+ };
270
+ }
271
+ return {
272
+ type: AuthErrorType.UNKNOWN_ERROR,
273
+ message,
274
+ originalError: error,
275
+ userMessage: 'An unexpected error occurred. Please try again.'
276
+ };
277
+ }
278
+ /**
279
+ * WorkOS includes the profile picture URL on the user object — no extra API call needed.
280
+ */
281
+ async getProfilePictureUrlInternal() {
282
+ return this.client?.getUser()?.profilePictureUrl ?? null;
283
+ }
284
+ /**
285
+ * No-op. WorkOS uses rotating refresh tokens, so it doesn't need interactive
286
+ * re-authentication when an access token expires; if refresh fails the base class surfaces
287
+ * the error and the user re-logs in manually.
288
+ */
289
+ async handleSessionExpiryInternal() {
290
+ return;
291
+ }
292
+ // ============================================================================
293
+ // HELPER METHODS
294
+ // ============================================================================
295
+ async ensureInitialized() {
296
+ if (!this.client) {
297
+ await this.initialize();
298
+ }
299
+ }
300
+ /**
301
+ * Map a WorkOS `User` to the framework-standard `StandardUserInfo`.
302
+ * WorkOS exposes `firstName`/`lastName` as `string | null`; we normalize null → undefined.
303
+ */
304
+ mapWorkOSUserToStandard(user) {
305
+ const fullName = [user.firstName, user.lastName].filter(Boolean).join(' ');
306
+ return {
307
+ id: user.id,
308
+ email: user.email,
309
+ name: fullName || user.email,
310
+ givenName: user.firstName ?? undefined,
311
+ familyName: user.lastName ?? undefined,
312
+ preferredUsername: user.email,
313
+ pictureUrl: user.profilePictureUrl ?? undefined,
314
+ emailVerified: user.emailVerified
315
+ };
316
+ }
317
+ // ============================================================================
318
+ // CONFIGURATION
319
+ // ============================================================================
320
+ getRequiredConfig() {
321
+ return ['clientId'];
322
+ }
323
+ validateConfig(config) {
324
+ // The real config arrives via the 'workosConfig' injection token; the only hard
325
+ // requirement is a clientId.
326
+ return !!this.workosConfig?.clientId || !!config['clientId'];
327
+ }
328
+ static ɵfac = function MJWorkOSProvider_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJWorkOSProvider)(i0.ɵɵinject('workosConfig')); };
329
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MJWorkOSProvider, factory: MJWorkOSProvider.ɵfac, providedIn: 'root' });
330
+ };
331
+ MJWorkOSProvider = MJWorkOSProvider_1 = __decorate([
332
+ RegisterClass(MJAuthBase, 'workos')
333
+ ], MJWorkOSProvider);
334
+ export { MJWorkOSProvider };
335
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJWorkOSProvider, [{
336
+ type: Injectable,
337
+ args: [{
338
+ providedIn: 'root'
339
+ }]
340
+ }], () => [{ type: undefined, decorators: [{
341
+ type: Inject,
342
+ args: ['workosConfig']
343
+ }] }], null); })();
344
+ //# sourceMappingURL=mjexplorer-workos-provider.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mjexplorer-workos-provider.service.js","sourceRoot":"","sources":["../../../src/lib/providers/mjexplorer-workos-provider.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, Inject, OnDestroy } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { MJAuthBase } from '../mjexplorer-auth-base.service';\nimport { AngularAuthProviderConfig } from '../IAuthProvider';\nimport {\n createClient,\n getClaims,\n User,\n LoginRequiredError,\n NoSessionError,\n RefreshTimeoutError\n} from '@workos-inc/authkit-js';\nimport {\n StandardUserInfo,\n StandardAuthToken,\n StandardAuthError,\n AuthErrorType,\n TokenRefreshResult\n} from '../auth-types';\n\n/**\n * The AuthKit client instance type. The `@workos-inc/authkit-js` package does not export its\n * `Client` class directly, so we derive the type from `createClient`'s resolved return value.\n */\ntype WorkOSClient = Awaited<ReturnType<typeof createClient>>;\n\n/**\n * Resolved configuration for the WorkOS AuthKit client.\n * Provided via the `'workosConfig'` injection token (see `angularProviderFactory`).\n */\nexport interface WorkOSAuthConfig {\n /** WorkOS AuthKit client ID (e.g. `client_01H...`). Required. */\n clientId: string;\n /** OAuth redirect URI registered in the WorkOS dashboard. Defaults to `window.location.origin`. */\n redirectUri?: string;\n /** Override the WorkOS API hostname (rarely needed; for proxies / custom domains). */\n apiHostname?: string;\n /** Enables AuthKit dev mode (localStorage session, no third-party cookies). */\n devMode?: boolean;\n}\n\n/**\n * WorkOS (AuthKit) authentication provider — browser side.\n *\n * Wraps the vanilla-JS `@workos-inc/authkit-js` SDK behind {@link MJAuthBase} so the rest of\n * MemberJunction never sees WorkOS-specific details. AuthKit issues a JWT **access token**\n * (returned by `getAccessToken()`) that MJ sends to the GraphQL API as a Bearer token; the\n * server validates it via `@memberjunction/auth-providers`' `WorkOSProvider`.\n *\n * Unlike the Auth0/MSAL/Okta providers, AuthKit is not an Angular library — there is no module\n * to import. This provider creates the client itself in {@link initialize} using config supplied\n * through the `'workosConfig'` injection token.\n *\n * > **Email note:** `getUser()` always returns the user's email for display here, but the\n * > **access token** only carries `email` if a WorkOS JWT Template adds it. MJ resolves users by\n * > email server-side, so configuring that template is required. See `WORKOS.md` in\n * > `@memberjunction/auth-providers`.\n */\n@Injectable({\n providedIn: 'root'\n})\n@RegisterClass(MJAuthBase, 'workos')\nexport class MJWorkOSProvider extends MJAuthBase implements OnDestroy {\n static readonly PROVIDER_TYPE = 'workos';\n readonly type = MJWorkOSProvider.PROVIDER_TYPE;\n\n private client: WorkOSClient | null = null;\n private initPromise: Promise<void> | null = null;\n\n /**\n * Factory function to provide the Angular dependencies required by WorkOS.\n * Stored as a static property so the factory can read it without instantiation.\n */\n static angularProviderFactory = (environment: Record<string, unknown>) => [\n {\n provide: 'workosConfig',\n useValue: {\n clientId: environment['WORKOS_CLIENTID'] as string,\n redirectUri: (environment['WORKOS_REDIRECT_URI'] as string) || window.location.origin,\n apiHostname: environment['WORKOS_API_HOSTNAME'] as string | undefined,\n devMode: (environment['WORKOS_DEV_MODE'] as boolean | undefined) ?? false\n } satisfies WorkOSAuthConfig\n }\n ];\n\n constructor(@Inject('workosConfig') private workosConfig: WorkOSAuthConfig) {\n const config: AngularAuthProviderConfig = {\n name: MJWorkOSProvider.PROVIDER_TYPE,\n type: MJWorkOSProvider.PROVIDER_TYPE\n };\n super(config);\n }\n\n // ============================================================================\n // LIFECYCLE METHODS\n // ============================================================================\n\n /**\n * Initialize the AuthKit client. `createClient()` constructs the client AND processes any\n * pending OAuth redirect (it reads the `code` from the URL and exchanges it), so once it\n * resolves the session — if any — is already established and `getUser()` is populated.\n *\n * Idempotent and concurrency-safe: parallel callers share a single in-flight promise.\n */\n async initialize(): Promise<void> {\n if (this.client) {\n return;\n }\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = this.createAndSyncClient();\n try {\n await this.initPromise;\n } finally {\n this.initPromise = null;\n }\n }\n\n private async createAndSyncClient(): Promise<void> {\n this.client = await createClient(this.workosConfig.clientId, {\n redirectUri: this.workosConfig.redirectUri,\n apiHostname: this.workosConfig.apiHostname,\n devMode: this.workosConfig.devMode,\n // Keep MJ's reactive auth state in lockstep with AuthKit's silent refresh cycle.\n onRefresh: () => this.syncSessionState(),\n onRefreshFailure: () => {\n this.updateAuthState(false);\n this.updateUserInfo(null);\n }\n });\n\n this.syncSessionState();\n }\n\n /**\n * Pushes the current AuthKit session into MJ's reactive streams.\n */\n private syncSessionState(): void {\n const user = this.client?.getUser() ?? null;\n if (user) {\n this.updateAuthState(true);\n this.updateUserInfo(this.mapWorkOSUserToStandard(user));\n } else {\n this.updateAuthState(false);\n this.updateUserInfo(null);\n }\n }\n\n protected async loginInternal(options?: Record<string, unknown>): Promise<void> {\n await this.ensureInitialized();\n // signIn redirects the browser to the WorkOS hosted login page. Any caller-supplied\n // options (loginHint, organizationId, state, …) are passed straight through.\n await this.client!.signIn(options);\n }\n\n protected async logoutInternal(): Promise<void> {\n // signOut clears the AuthKit session and redirects to the app's configured Logout URI.\n // If there is no active session it throws NoSessionError — provide returnTo so the user is\n // still returned to the app rather than left on a dead page.\n this.updateAuthState(false);\n this.updateUserInfo(null);\n try {\n this.client?.signOut({ returnTo: this.workosConfig.redirectUri || window.location.origin });\n } catch (error) {\n // NoSessionError (no active session) — just hard-redirect home.\n if (error instanceof NoSessionError) {\n window.location.href = window.location.origin;\n } else {\n throw error;\n }\n }\n }\n\n async handleCallback(): Promise<void> {\n // AuthKit's createClient() processes the redirect internally during initialize().\n // This method exists for interface compatibility (mirrors the Auth0 provider).\n await this.ensureInitialized();\n }\n\n ngOnDestroy(): void {\n // Release the AuthKit refresh timer / listeners.\n this.client?.dispose();\n this.client = null;\n }\n\n // ============================================================================\n // ABSTRACT METHOD IMPLEMENTATIONS\n // ============================================================================\n\n /**\n * Extract the access token (a JWT) from AuthKit. `getAccessToken()` returns a valid token,\n * silently refreshing first if the current one is near expiry. Throws when there is no\n * session — we translate that to `null` so callers can treat it as \"not authenticated\".\n */\n protected async extractIdTokenInternal(): Promise<string | null> {\n if (!this.client) {\n return null;\n }\n try {\n return await this.client.getAccessToken();\n } catch (error) {\n if (error instanceof LoginRequiredError) {\n return null;\n }\n console.error('[WorkOS] Error extracting access token:', error);\n return null;\n }\n }\n\n /**\n * Extract complete token info. The expiry comes from the JWT's `exp` claim, decoded with the\n * SDK's `getClaims()` helper.\n */\n protected async extractTokenInfoInternal(): Promise<StandardAuthToken | null> {\n const accessToken = await this.extractIdTokenInternal();\n if (!accessToken) {\n return null;\n }\n\n let expiresAt = 0;\n try {\n const claims = getClaims(accessToken);\n expiresAt = claims.exp ? claims.exp * 1000 : 0; // exp is seconds → milliseconds\n } catch (error) {\n console.warn('[WorkOS] Could not decode access token claims:', error);\n }\n\n return {\n idToken: accessToken,\n accessToken,\n expiresAt\n };\n }\n\n /**\n * Extract user info from AuthKit's in-memory user (always populated when authenticated,\n * regardless of which claims the access token carries).\n */\n protected async extractUserInfoInternal(): Promise<StandardUserInfo | null> {\n const user = this.client?.getUser() ?? null;\n return user ? this.mapWorkOSUserToStandard(user) : null;\n }\n\n /**\n * Force a token refresh through AuthKit's refresh-token rotation.\n */\n protected async refreshTokenInternal(): Promise<TokenRefreshResult> {\n if (!this.client) {\n return {\n success: false,\n error: {\n type: AuthErrorType.NO_ACTIVE_SESSION,\n message: 'WorkOS client not initialized',\n userMessage: 'You need to log in to continue.'\n }\n };\n }\n\n try {\n const accessToken = await this.client.getAccessToken({ forceRefresh: true });\n this.syncSessionState();\n\n let expiresAt = 0;\n try {\n const claims = getClaims(accessToken);\n expiresAt = claims.exp ? claims.exp * 1000 : 0;\n } catch {\n // exp decode is best-effort; a missing value just means the consumer can't pre-empt expiry.\n }\n\n return {\n success: true,\n token: { idToken: accessToken, accessToken, expiresAt }\n };\n } catch (error) {\n return {\n success: false,\n error: this.classifyErrorInternal(error)\n };\n }\n }\n\n /**\n * Map WorkOS / AuthKit SDK errors to semantic `AuthErrorType` values.\n */\n protected classifyErrorInternal(error: unknown): StandardAuthError {\n if (error instanceof LoginRequiredError || error instanceof NoSessionError) {\n return {\n type: AuthErrorType.NO_ACTIVE_SESSION,\n message: (error as Error).message,\n originalError: error,\n userMessage: 'You need to log in to continue.'\n };\n }\n\n // RefreshTimeoutError is exported; its parent `RefreshError` is not, so we also match by\n // constructor name to catch any non-timeout refresh failure.\n if (error instanceof RefreshTimeoutError || (error as Error)?.name === 'RefreshError') {\n return {\n type: AuthErrorType.TOKEN_EXPIRED,\n message: (error as Error).message,\n originalError: error,\n userMessage: 'Your session has expired. Please log in again.'\n };\n }\n\n const message = (error as { message?: string })?.message || 'Unknown error';\n if (message.includes('network') || message.includes('fetch') || message.includes('Failed to fetch')) {\n return {\n type: AuthErrorType.NETWORK_ERROR,\n message,\n originalError: error,\n userMessage: 'Network error. Please check your connection and try again.'\n };\n }\n\n return {\n type: AuthErrorType.UNKNOWN_ERROR,\n message,\n originalError: error,\n userMessage: 'An unexpected error occurred. Please try again.'\n };\n }\n\n /**\n * WorkOS includes the profile picture URL on the user object — no extra API call needed.\n */\n protected async getProfilePictureUrlInternal(): Promise<string | null> {\n return this.client?.getUser()?.profilePictureUrl ?? null;\n }\n\n /**\n * No-op. WorkOS uses rotating refresh tokens, so it doesn't need interactive\n * re-authentication when an access token expires; if refresh fails the base class surfaces\n * the error and the user re-logs in manually.\n */\n protected async handleSessionExpiryInternal(): Promise<void> {\n return;\n }\n\n // ============================================================================\n // HELPER METHODS\n // ============================================================================\n\n private async ensureInitialized(): Promise<void> {\n if (!this.client) {\n await this.initialize();\n }\n }\n\n /**\n * Map a WorkOS `User` to the framework-standard `StandardUserInfo`.\n * WorkOS exposes `firstName`/`lastName` as `string | null`; we normalize null → undefined.\n */\n private mapWorkOSUserToStandard(user: User): StandardUserInfo {\n const fullName = [user.firstName, user.lastName].filter(Boolean).join(' ');\n return {\n id: user.id,\n email: user.email,\n name: fullName || user.email,\n givenName: user.firstName ?? undefined,\n familyName: user.lastName ?? undefined,\n preferredUsername: user.email,\n pictureUrl: user.profilePictureUrl ?? undefined,\n emailVerified: user.emailVerified\n };\n }\n\n // ============================================================================\n // CONFIGURATION\n // ============================================================================\n\n override getRequiredConfig(): string[] {\n return ['clientId'];\n }\n\n override validateConfig(config: Record<string, unknown>): boolean {\n // The real config arrives via the 'workosConfig' injection token; the only hard\n // requirement is a clientId.\n return !!this.workosConfig?.clientId || !!config['clientId'];\n }\n}\n"]}
@@ -8,5 +8,6 @@ export * from './lib/providers/mjexplorer-msal-provider.service';
8
8
  export * from './lib/providers/mjexplorer-auth0-provider.service';
9
9
  export * from './lib/providers/mjexplorer-okta-provider.service';
10
10
  export * from './lib/providers/mjexplorer-cognito-provider.service';
11
+ export * from './lib/providers/mjexplorer-workos-provider.service';
11
12
  export * from './lib/providers/mjexplorer-magic-link-provider.service';
12
13
  //# sourceMappingURL=public-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,cAAc,kBAAkB,CAAC;AAGjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AAGjD,cAAc,kDAAkD,CAAC;AACjE,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,wDAAwD,CAAC"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,cAAc,kBAAkB,CAAC;AAGjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AAGjD,cAAc,kDAAkD,CAAC;AACjE,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,oDAAoD,CAAC;AACnE,cAAc,wDAAwD,CAAC"}
@@ -15,5 +15,6 @@ export * from './lib/providers/mjexplorer-msal-provider.service';
15
15
  export * from './lib/providers/mjexplorer-auth0-provider.service';
16
16
  export * from './lib/providers/mjexplorer-okta-provider.service';
17
17
  export * from './lib/providers/mjexplorer-cognito-provider.service';
18
+ export * from './lib/providers/mjexplorer-workos-provider.service';
18
19
  export * from './lib/providers/mjexplorer-magic-link-provider.service';
19
20
  //# sourceMappingURL=public-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAe;AACf,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,2CAA2C;AAC3C,cAAc,kBAAkB,CAAC;AAEjC,oBAAoB;AACpB,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AAEjD,2BAA2B;AAC3B,cAAc,kDAAkD,CAAC;AACjE,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,wDAAwD,CAAC","sourcesContent":["/*\n * Public API Surface\n */\n\n// Core exports\nexport * from './lib/mjexplorer-auth-base.service';\nexport * from './lib/auth-services.module';\nexport { RedirectComponent } from './lib/redirect.component';\n\n// Type exports - v3.0.0 standardized types\nexport * from './lib/auth-types';\n\n// Interface exports\nexport * from './lib/IAuthProvider';\nexport * from './lib/AngularAuthProviderFactory';\n\n// Provider implementations\nexport * from './lib/providers/mjexplorer-msal-provider.service';\nexport * from './lib/providers/mjexplorer-auth0-provider.service';\nexport * from './lib/providers/mjexplorer-okta-provider.service';\nexport * from './lib/providers/mjexplorer-cognito-provider.service';\nexport * from './lib/providers/mjexplorer-magic-link-provider.service';"]}
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAe;AACf,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,2CAA2C;AAC3C,cAAc,kBAAkB,CAAC;AAEjC,oBAAoB;AACpB,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AAEjD,2BAA2B;AAC3B,cAAc,kDAAkD,CAAC;AACjE,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,oDAAoD,CAAC;AACnE,cAAc,wDAAwD,CAAC","sourcesContent":["/*\n * Public API Surface\n */\n\n// Core exports\nexport * from './lib/mjexplorer-auth-base.service';\nexport * from './lib/auth-services.module';\nexport { RedirectComponent } from './lib/redirect.component';\n\n// Type exports - v3.0.0 standardized types\nexport * from './lib/auth-types';\n\n// Interface exports\nexport * from './lib/IAuthProvider';\nexport * from './lib/AngularAuthProviderFactory';\n\n// Provider implementations\nexport * from './lib/providers/mjexplorer-msal-provider.service';\nexport * from './lib/providers/mjexplorer-auth0-provider.service';\nexport * from './lib/providers/mjexplorer-okta-provider.service';\nexport * from './lib/providers/mjexplorer-cognito-provider.service';\nexport * from './lib/providers/mjexplorer-workos-provider.service';\nexport * from './lib/providers/mjexplorer-magic-link-provider.service';"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-auth-services",
3
- "version": "5.42.0",
3
+ "version": "5.44.0",
4
4
  "description": "MemberJunction Explorer: Authentication Services",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -28,11 +28,12 @@
28
28
  "@azure/msal-browser": "^5.1.0",
29
29
  "@azure/msal-common": "^16.0.3",
30
30
  "@okta/okta-auth-js": "^7.14.1",
31
+ "@workos-inc/authkit-js": "^0.20.1",
31
32
  "aws-amplify": "^6.0.0"
32
33
  },
33
34
  "dependencies": {
34
- "@memberjunction/core": "5.42.0",
35
- "@memberjunction/global": "5.42.0",
35
+ "@memberjunction/core": "5.44.0",
36
+ "@memberjunction/global": "5.44.0",
36
37
  "rxjs": "^7.8.2",
37
38
  "tslib": "^2.8.1"
38
39
  },
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=auth-services.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-services.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/auth-services.test.ts"],"names":[],"mappings":""}
@@ -1,382 +0,0 @@
1
- /**
2
- * Tests for auth-services package:
3
- * - AngularAuthProviderFactory
4
- * - Auth types
5
- * - MJAuthBase (via dynamic import)
6
- */
7
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
- // Mock Angular
9
- vi.mock('@angular/core', () => ({
10
- Injectable: () => (target) => target,
11
- Injector: class {
12
- },
13
- }));
14
- const __mockGlobalObjectStore = {};
15
- vi.mock('@memberjunction/global', () => ({
16
- MJGlobal: {
17
- Instance: {
18
- ClassFactory: {
19
- CreateInstance: vi.fn(),
20
- GetRegistrationsByRootClass: vi.fn(() => []),
21
- GetRegistration: vi.fn(),
22
- },
23
- }
24
- },
25
- GetGlobalObjectStore: () => __mockGlobalObjectStore,
26
- }));
27
- vi.mock('@memberjunction/core', () => ({
28
- AuthProviderConfig: class {
29
- },
30
- }));
31
- vi.mock('rxjs', async () => {
32
- const actual = await vi.importActual('rxjs');
33
- return actual;
34
- });
35
- // ======================= Auth types =======================
36
- describe('AuthErrorType enum', () => {
37
- it('should have expected error types', async () => {
38
- const { AuthErrorType } = await import('../auth-types');
39
- expect(AuthErrorType.TOKEN_EXPIRED).toBe('TOKEN_EXPIRED');
40
- expect(AuthErrorType.NO_ACTIVE_SESSION).toBe('NO_ACTIVE_SESSION');
41
- expect(AuthErrorType.INTERACTION_REQUIRED).toBe('INTERACTION_REQUIRED');
42
- expect(AuthErrorType.USER_CANCELLED).toBe('USER_CANCELLED');
43
- expect(AuthErrorType.NETWORK_ERROR).toBe('NETWORK_ERROR');
44
- expect(AuthErrorType.CONFIGURATION_ERROR).toBe('CONFIGURATION_ERROR');
45
- expect(AuthErrorType.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');
46
- });
47
- });
48
- // ======================= IAuthProvider interface =======================
49
- describe('IAuthProvider interface', () => {
50
- it('should export the interface module', async () => {
51
- const mod = await import('../IAuthProvider');
52
- expect(mod).toBeDefined();
53
- });
54
- });
55
- // ======================= MJAuthBase =======================
56
- describe('MJAuthBase', () => {
57
- let MJAuthBase;
58
- beforeEach(async () => {
59
- // Mock window before importing
60
- global.window = { location: { pathname: '/test', search: '?q=1' } };
61
- const mod = await import('../mjexplorer-auth-base.service');
62
- MJAuthBase = mod.MJAuthBase;
63
- });
64
- it('should be importable', () => {
65
- expect(MJAuthBase).toBeDefined();
66
- });
67
- it('should capture initial window location in constructor', () => {
68
- // Create a concrete test subclass inline
69
- class TestProvider extends MJAuthBase {
70
- type = 'test';
71
- async initialize() { }
72
- async loginInternal() { }
73
- async logoutInternal() { }
74
- async handleCallback() { }
75
- async extractIdTokenInternal() { return 'token'; }
76
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
77
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
78
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
79
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
80
- async getProfilePictureUrlInternal() { return null; }
81
- async handleSessionExpiryInternal() { }
82
- }
83
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
84
- expect(provider.initialPath).toBe('/test');
85
- expect(provider.initialSearch).toBe('?q=1');
86
- });
87
- it('should validate config with clientId', () => {
88
- class TestProvider extends MJAuthBase {
89
- type = 'test';
90
- async initialize() { }
91
- async loginInternal() { }
92
- async logoutInternal() { }
93
- async handleCallback() { }
94
- async extractIdTokenInternal() { return 'token'; }
95
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
96
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
97
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
98
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
99
- async getProfilePictureUrlInternal() { return null; }
100
- async handleSessionExpiryInternal() { }
101
- }
102
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
103
- expect(provider.validateConfig({ clientId: 'my-client' })).toBe(true);
104
- expect(provider.validateConfig({})).toBe(false);
105
- expect(provider.validateConfig({ clientId: '' })).toBe(false);
106
- });
107
- it('should return clientId as required config', () => {
108
- class TestProvider extends MJAuthBase {
109
- type = 'test';
110
- async initialize() { }
111
- async loginInternal() { }
112
- async logoutInternal() { }
113
- async handleCallback() { }
114
- async extractIdTokenInternal() { return 'token'; }
115
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
116
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
117
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
118
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
119
- async getProfilePictureUrlInternal() { return null; }
120
- async handleSessionExpiryInternal() { }
121
- }
122
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
123
- expect(provider.getRequiredConfig()).toEqual(['clientId']);
124
- });
125
- it('should emit false for isAuthenticated initially', () => {
126
- class TestProvider extends MJAuthBase {
127
- type = 'test';
128
- async initialize() { }
129
- async loginInternal() { }
130
- async logoutInternal() { }
131
- async handleCallback() { }
132
- async extractIdTokenInternal() { return 'token'; }
133
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
134
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
135
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
136
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
137
- async getProfilePictureUrlInternal() { return null; }
138
- async handleSessionExpiryInternal() { }
139
- }
140
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
141
- let value;
142
- provider.isAuthenticated().subscribe(v => value = v);
143
- expect(value).toBe(false);
144
- });
145
- it('should emit null for getUserInfo initially', () => {
146
- class TestProvider extends MJAuthBase {
147
- type = 'test';
148
- async initialize() { }
149
- async loginInternal() { }
150
- async logoutInternal() { }
151
- async handleCallback() { }
152
- async extractIdTokenInternal() { return 'token'; }
153
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
154
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
155
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
156
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
157
- async getProfilePictureUrlInternal() { return null; }
158
- async handleSessionExpiryInternal() { }
159
- }
160
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
161
- let value = 'not-null';
162
- provider.getUserInfo().subscribe(v => value = v);
163
- expect(value).toBeNull();
164
- });
165
- it('should delegate classifyError to classifyErrorInternal', () => {
166
- class TestProvider extends MJAuthBase {
167
- type = 'test';
168
- async initialize() { }
169
- async loginInternal() { }
170
- async logoutInternal() { }
171
- async handleCallback() { }
172
- async extractIdTokenInternal() { return 'token'; }
173
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
174
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
175
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
176
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'Custom error msg' }; }
177
- async getProfilePictureUrlInternal() { return null; }
178
- async handleSessionExpiryInternal() { }
179
- }
180
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
181
- const result = provider.classifyError(new Error('test'));
182
- expect(result.type).toBe('UNKNOWN_ERROR');
183
- expect(result.message).toBe('Custom error msg');
184
- });
185
- it('should return token from getIdToken', async () => {
186
- class TestProvider extends MJAuthBase {
187
- type = 'test';
188
- async initialize() { }
189
- async loginInternal() { }
190
- async logoutInternal() { }
191
- async handleCallback() { }
192
- async extractIdTokenInternal() { return 'my-test-token'; }
193
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
194
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
195
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
196
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
197
- async getProfilePictureUrlInternal() { return null; }
198
- async handleSessionExpiryInternal() { }
199
- }
200
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
201
- const token = await provider.getIdToken();
202
- expect(token).toBe('my-test-token');
203
- });
204
- it('should return an observable from login', () => {
205
- class TestProvider extends MJAuthBase {
206
- type = 'test';
207
- async initialize() { }
208
- async loginInternal() { }
209
- async logoutInternal() { }
210
- async handleCallback() { }
211
- async extractIdTokenInternal() { return 'token'; }
212
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
213
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
214
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
215
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
216
- async getProfilePictureUrlInternal() { return null; }
217
- async handleSessionExpiryInternal() { }
218
- }
219
- const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
220
- const result = provider.login();
221
- expect(result).toBeDefined();
222
- expect(typeof result.subscribe).toBe('function');
223
- });
224
- });
225
- // ======================= logout and cache clearing =======================
226
- describe('logout and cache clearing', () => {
227
- let MJAuthBase;
228
- beforeEach(async () => {
229
- global.window = { location: { pathname: '/test', search: '' } };
230
- const mod = await import('../mjexplorer-auth-base.service');
231
- MJAuthBase = mod.MJAuthBase;
232
- });
233
- afterEach(() => {
234
- vi.unstubAllGlobals();
235
- });
236
- /**
237
- * Build a minimal TestProvider. The logoutSpy tracks logoutInternal calls.
238
- * Pass preservedKeys to override the default preservedLocalStorageKeys getter.
239
- */
240
- function makeProvider(preservedKeys) {
241
- const logoutSpy = vi.fn().mockResolvedValue(undefined);
242
- class TestProvider extends MJAuthBase {
243
- type = 'test';
244
- logoutSpy = logoutSpy;
245
- async initialize() { }
246
- async loginInternal() { }
247
- async logoutInternal() { return logoutSpy(); }
248
- async handleCallback() { }
249
- async extractIdTokenInternal() { return 'token'; }
250
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
251
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
252
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
253
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
254
- async getProfilePictureUrlInternal() { return null; }
255
- async handleSessionExpiryInternal() { }
256
- get preservedLocalStorageKeys() {
257
- return preservedKeys ?? super.preservedLocalStorageKeys;
258
- }
259
- }
260
- return new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });
261
- }
262
- /** Stub localStorage with a fixed set of key/value pairs, return a spy on removeItem. */
263
- function stubLocalStorage(items) {
264
- const keys = Object.keys(items);
265
- const removeItem = vi.fn();
266
- vi.stubGlobal('localStorage', {
267
- length: keys.length,
268
- key: (i) => keys[i] ?? null,
269
- removeItem,
270
- });
271
- return { removeItem };
272
- }
273
- /** Stub indexedDB.deleteDatabase to resolve immediately via onsuccess. */
274
- function stubIndexedDB() {
275
- const deleteDatabase = vi.fn(() => {
276
- const req = {};
277
- Object.defineProperty(req, 'onsuccess', { configurable: true, set(fn) { fn(); } });
278
- Object.defineProperty(req, 'onerror', { configurable: true, set() { } });
279
- Object.defineProperty(req, 'onblocked', { configurable: true, set() { } });
280
- return req;
281
- });
282
- vi.stubGlobal('indexedDB', { deleteDatabase });
283
- return { deleteDatabase };
284
- }
285
- it('should call logoutInternal during logout', async () => {
286
- stubLocalStorage({});
287
- stubIndexedDB();
288
- const provider = makeProvider();
289
- await provider.logout();
290
- expect(provider.logoutSpy).toHaveBeenCalledOnce();
291
- });
292
- it('should remove non-preserved localStorage keys on logout', async () => {
293
- const { removeItem } = stubLocalStorage({
294
- 'mj-session-id': 'abc',
295
- 'auth0.token': 'xyz',
296
- 'mj-theme': 'dark',
297
- });
298
- stubIndexedDB();
299
- await makeProvider().logout();
300
- expect(removeItem).toHaveBeenCalledWith('mj-session-id');
301
- expect(removeItem).toHaveBeenCalledWith('auth0.token');
302
- expect(removeItem).not.toHaveBeenCalledWith('mj-theme');
303
- });
304
- it('should preserve mj-theme by default even when it is the only key', async () => {
305
- const { removeItem } = stubLocalStorage({ 'mj-theme': 'dark' });
306
- stubIndexedDB();
307
- await makeProvider().logout();
308
- expect(removeItem).not.toHaveBeenCalled();
309
- });
310
- it('should delete the MJ_Metadata IndexedDB on logout', async () => {
311
- stubLocalStorage({});
312
- const { deleteDatabase } = stubIndexedDB();
313
- await makeProvider().logout();
314
- expect(deleteDatabase).toHaveBeenCalledWith('MJ_Metadata');
315
- });
316
- it('should clear caches before calling logoutInternal', async () => {
317
- const callOrder = [];
318
- const deleteDatabase = vi.fn(() => {
319
- callOrder.push('clearCaches');
320
- const req = {};
321
- Object.defineProperty(req, 'onsuccess', { configurable: true, set(fn) { fn(); } });
322
- Object.defineProperty(req, 'onerror', { configurable: true, set() { } });
323
- Object.defineProperty(req, 'onblocked', { configurable: true, set() { } });
324
- return req;
325
- });
326
- vi.stubGlobal('indexedDB', { deleteDatabase });
327
- stubLocalStorage({});
328
- const logoutSpy = vi.fn().mockImplementation(async () => {
329
- callOrder.push('logoutInternal');
330
- });
331
- class OrderTestProvider extends MJAuthBase {
332
- type = 'test';
333
- async initialize() { }
334
- async loginInternal() { }
335
- async logoutInternal() { return logoutSpy(); }
336
- async handleCallback() { }
337
- async extractIdTokenInternal() { return 'token'; }
338
- async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }
339
- async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }
340
- async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }
341
- classifyErrorInternal() { return { type: 'UNKNOWN_ERROR', message: 'err' }; }
342
- async getProfilePictureUrlInternal() { return null; }
343
- async handleSessionExpiryInternal() { }
344
- }
345
- await new OrderTestProvider({ name: 'test', type: 'test', clientId: 'test-client' }).logout();
346
- expect(callOrder).toEqual(['clearCaches', 'logoutInternal']);
347
- });
348
- it('should respect overridden preservedLocalStorageKeys in a subclass', async () => {
349
- const { removeItem } = stubLocalStorage({
350
- 'mj-theme': 'dark',
351
- 'my-app-setting': 'value',
352
- 'session-data': 'to-remove',
353
- });
354
- stubIndexedDB();
355
- await makeProvider(new Set(['mj-theme', 'my-app-setting'])).logout();
356
- expect(removeItem).toHaveBeenCalledWith('session-data');
357
- expect(removeItem).not.toHaveBeenCalledWith('mj-theme');
358
- expect(removeItem).not.toHaveBeenCalledWith('my-app-setting');
359
- });
360
- });
361
- // ======================= AngularAuthProviderFactory =======================
362
- describe('AngularAuthProviderFactory', () => {
363
- let Factory;
364
- beforeEach(async () => {
365
- const mod = await import('../AngularAuthProviderFactory');
366
- Factory = mod.AngularAuthProviderFactory;
367
- });
368
- it('should create a new factory instance', () => {
369
- const factory = new Factory({ get: vi.fn() });
370
- expect(factory).toBeDefined();
371
- });
372
- it('should return undefined for unregistered provider', () => {
373
- const factory = new Factory({ get: vi.fn() });
374
- expect(factory.getProvider('nonexistent')).toBeUndefined();
375
- });
376
- it('should clear providers', () => {
377
- const factory = new Factory({ get: vi.fn() });
378
- factory.clearProviders();
379
- expect(factory.getProvider('test')).toBeUndefined();
380
- });
381
- });
382
- //# sourceMappingURL=auth-services.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-services.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/auth-services.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tests for auth-services package:\n * - AngularAuthProviderFactory\n * - Auth types\n * - MJAuthBase (via dynamic import)\n */\nimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { AuthErrorType } from '../auth-types';\n\n// Mock Angular\nvi.mock('@angular/core', () => ({\n Injectable: () => (target: Function) => target,\n Injector: class {},\n}));\n\nconst __mockGlobalObjectStore: Record<string, unknown> = {};\nvi.mock('@memberjunction/global', () => ({\n MJGlobal: {\n Instance: {\n ClassFactory: {\n CreateInstance: vi.fn(),\n GetRegistrationsByRootClass: vi.fn(() => []),\n GetRegistration: vi.fn(),\n },\n }\n },\n GetGlobalObjectStore: () => __mockGlobalObjectStore,\n}));\n\nvi.mock('@memberjunction/core', () => ({\n AuthProviderConfig: class {},\n}));\n\nvi.mock('rxjs', async () => {\n const actual = await vi.importActual<typeof import('rxjs')>('rxjs');\n return actual;\n});\n\n// ======================= Auth types =======================\ndescribe('AuthErrorType enum', () => {\n it('should have expected error types', async () => {\n const { AuthErrorType } = await import('../auth-types');\n expect(AuthErrorType.TOKEN_EXPIRED).toBe('TOKEN_EXPIRED');\n expect(AuthErrorType.NO_ACTIVE_SESSION).toBe('NO_ACTIVE_SESSION');\n expect(AuthErrorType.INTERACTION_REQUIRED).toBe('INTERACTION_REQUIRED');\n expect(AuthErrorType.USER_CANCELLED).toBe('USER_CANCELLED');\n expect(AuthErrorType.NETWORK_ERROR).toBe('NETWORK_ERROR');\n expect(AuthErrorType.CONFIGURATION_ERROR).toBe('CONFIGURATION_ERROR');\n expect(AuthErrorType.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');\n });\n});\n\n// ======================= IAuthProvider interface =======================\ndescribe('IAuthProvider interface', () => {\n it('should export the interface module', async () => {\n const mod = await import('../IAuthProvider');\n expect(mod).toBeDefined();\n });\n});\n\n// ======================= MJAuthBase =======================\ndescribe('MJAuthBase', () => {\n let MJAuthBase: typeof import('../mjexplorer-auth-base.service').MJAuthBase;\n\n beforeEach(async () => {\n // Mock window before importing\n (global as Record<string, unknown>).window = { location: { pathname: '/test', search: '?q=1' } };\n\n const mod = await import('../mjexplorer-auth-base.service');\n MJAuthBase = mod.MJAuthBase;\n });\n\n it('should be importable', () => {\n expect(MJAuthBase).toBeDefined();\n });\n\n it('should capture initial window location in constructor', () => {\n // Create a concrete test subclass inline\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n expect(provider.initialPath).toBe('/test');\n expect(provider.initialSearch).toBe('?q=1');\n });\n\n it('should validate config with clientId', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n expect(provider.validateConfig({ clientId: 'my-client' })).toBe(true);\n expect(provider.validateConfig({})).toBe(false);\n expect(provider.validateConfig({ clientId: '' })).toBe(false);\n });\n\n it('should return clientId as required config', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n expect(provider.getRequiredConfig()).toEqual(['clientId']);\n });\n\n it('should emit false for isAuthenticated initially', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n let value: boolean | undefined;\n provider.isAuthenticated().subscribe(v => value = v);\n expect(value).toBe(false);\n });\n\n it('should emit null for getUserInfo initially', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n let value: unknown = 'not-null';\n provider.getUserInfo().subscribe(v => value = v);\n expect(value).toBeNull();\n });\n\n it('should delegate classifyError to classifyErrorInternal', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'Custom error msg' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n const result = provider.classifyError(new Error('test'));\n expect(result.type).toBe('UNKNOWN_ERROR');\n expect(result.message).toBe('Custom error msg');\n });\n\n it('should return token from getIdToken', async () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'my-test-token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n const token = await provider.getIdToken();\n expect(token).toBe('my-test-token');\n });\n\n it('should return an observable from login', () => {\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> {}\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n\n const provider = new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n const result = provider.login();\n expect(result).toBeDefined();\n expect(typeof result.subscribe).toBe('function');\n });\n});\n\n// ======================= logout and cache clearing =======================\ndescribe('logout and cache clearing', () => {\n let MJAuthBase: typeof import('../mjexplorer-auth-base.service').MJAuthBase;\n\n beforeEach(async () => {\n (global as Record<string, unknown>).window = { location: { pathname: '/test', search: '' } };\n const mod = await import('../mjexplorer-auth-base.service');\n MJAuthBase = mod.MJAuthBase;\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n /**\n * Build a minimal TestProvider. The logoutSpy tracks logoutInternal calls.\n * Pass preservedKeys to override the default preservedLocalStorageKeys getter.\n */\n function makeProvider(preservedKeys?: Set<string>) {\n const logoutSpy = vi.fn<() => Promise<void>>().mockResolvedValue(undefined);\n class TestProvider extends MJAuthBase {\n readonly type = 'test';\n readonly logoutSpy = logoutSpy;\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> { return logoutSpy(); }\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true as const, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n override get preservedLocalStorageKeys(): Set<string> {\n return preservedKeys ?? super.preservedLocalStorageKeys;\n }\n }\n return new TestProvider({ name: 'test', type: 'test', clientId: 'test-client' });\n }\n\n /** Stub localStorage with a fixed set of key/value pairs, return a spy on removeItem. */\n function stubLocalStorage(items: Record<string, string>) {\n const keys = Object.keys(items);\n const removeItem = vi.fn();\n vi.stubGlobal('localStorage', {\n length: keys.length,\n key: (i: number) => keys[i] ?? null,\n removeItem,\n });\n return { removeItem };\n }\n\n /** Stub indexedDB.deleteDatabase to resolve immediately via onsuccess. */\n function stubIndexedDB() {\n const deleteDatabase = vi.fn(() => {\n const req = {} as Record<string, unknown>;\n Object.defineProperty(req, 'onsuccess', { configurable: true, set(fn: () => void) { fn(); } });\n Object.defineProperty(req, 'onerror', { configurable: true, set() {} });\n Object.defineProperty(req, 'onblocked', { configurable: true, set() {} });\n return req;\n });\n vi.stubGlobal('indexedDB', { deleteDatabase });\n return { deleteDatabase };\n }\n\n it('should call logoutInternal during logout', async () => {\n stubLocalStorage({});\n stubIndexedDB();\n const provider = makeProvider();\n await provider.logout();\n expect(provider.logoutSpy).toHaveBeenCalledOnce();\n });\n\n it('should remove non-preserved localStorage keys on logout', async () => {\n const { removeItem } = stubLocalStorage({\n 'mj-session-id': 'abc',\n 'auth0.token': 'xyz',\n 'mj-theme': 'dark',\n });\n stubIndexedDB();\n await makeProvider().logout();\n expect(removeItem).toHaveBeenCalledWith('mj-session-id');\n expect(removeItem).toHaveBeenCalledWith('auth0.token');\n expect(removeItem).not.toHaveBeenCalledWith('mj-theme');\n });\n\n it('should preserve mj-theme by default even when it is the only key', async () => {\n const { removeItem } = stubLocalStorage({ 'mj-theme': 'dark' });\n stubIndexedDB();\n await makeProvider().logout();\n expect(removeItem).not.toHaveBeenCalled();\n });\n\n it('should delete the MJ_Metadata IndexedDB on logout', async () => {\n stubLocalStorage({});\n const { deleteDatabase } = stubIndexedDB();\n await makeProvider().logout();\n expect(deleteDatabase).toHaveBeenCalledWith('MJ_Metadata');\n });\n\n it('should clear caches before calling logoutInternal', async () => {\n const callOrder: string[] = [];\n const deleteDatabase = vi.fn(() => {\n callOrder.push('clearCaches');\n const req = {} as Record<string, unknown>;\n Object.defineProperty(req, 'onsuccess', { configurable: true, set(fn: () => void) { fn(); } });\n Object.defineProperty(req, 'onerror', { configurable: true, set() {} });\n Object.defineProperty(req, 'onblocked', { configurable: true, set() {} });\n return req;\n });\n vi.stubGlobal('indexedDB', { deleteDatabase });\n stubLocalStorage({});\n\n const logoutSpy = vi.fn<() => Promise<void>>().mockImplementation(async () => {\n callOrder.push('logoutInternal');\n });\n class OrderTestProvider extends MJAuthBase {\n readonly type = 'test';\n async initialize(): Promise<void> {}\n protected async loginInternal(): Promise<void> {}\n protected async logoutInternal(): Promise<void> { return logoutSpy(); }\n async handleCallback(): Promise<void> {}\n protected async extractIdTokenInternal() { return 'token'; }\n protected async extractTokenInfoInternal() { return { idToken: 'token', expiresAt: 0 }; }\n protected async extractUserInfoInternal() { return { id: '1', email: 'a@b.c', name: 'Test' }; }\n protected async refreshTokenInternal() { return { success: true as const, token: { idToken: 'token', expiresAt: 0 } }; }\n protected classifyErrorInternal() { return { type: 'UNKNOWN_ERROR' as AuthErrorType, message: 'err' }; }\n protected async getProfilePictureUrlInternal() { return null; }\n protected async handleSessionExpiryInternal() {}\n }\n await new OrderTestProvider({ name: 'test', type: 'test', clientId: 'test-client' }).logout();\n expect(callOrder).toEqual(['clearCaches', 'logoutInternal']);\n });\n\n it('should respect overridden preservedLocalStorageKeys in a subclass', async () => {\n const { removeItem } = stubLocalStorage({\n 'mj-theme': 'dark',\n 'my-app-setting': 'value',\n 'session-data': 'to-remove',\n });\n stubIndexedDB();\n await makeProvider(new Set(['mj-theme', 'my-app-setting'])).logout();\n expect(removeItem).toHaveBeenCalledWith('session-data');\n expect(removeItem).not.toHaveBeenCalledWith('mj-theme');\n expect(removeItem).not.toHaveBeenCalledWith('my-app-setting');\n });\n});\n\n// ======================= AngularAuthProviderFactory =======================\ndescribe('AngularAuthProviderFactory', () => {\n let Factory: typeof import('../AngularAuthProviderFactory').AngularAuthProviderFactory;\n\n beforeEach(async () => {\n const mod = await import('../AngularAuthProviderFactory');\n Factory = mod.AngularAuthProviderFactory;\n });\n\n it('should create a new factory instance', () => {\n const factory = new Factory({ get: vi.fn() } as never);\n expect(factory).toBeDefined();\n });\n\n it('should return undefined for unregistered provider', () => {\n const factory = new Factory({ get: vi.fn() } as never);\n expect(factory.getProvider('nonexistent')).toBeUndefined();\n });\n\n it('should clear providers', () => {\n const factory = new Factory({ get: vi.fn() } as never);\n factory.clearProviders();\n expect(factory.getProvider('test')).toBeUndefined();\n });\n});\n"]}