@memberjunction/ng-auth-services 5.38.0 → 5.40.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.
@@ -1 +1 @@
1
- {"version":3,"file":"auth-services.module.d.ts","sourceRoot":"","sources":["../../src/lib/auth-services.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAO9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;;;AAGzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;GAGG;AACH,qBAKa,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,GAAG,mBAAmB,CAAC,kBAAkB,CAAC;yCAD9D,kBAAkB;0CAAlB,kBAAkB;0CAAlB,kBAAkB;CA4C9B"}
1
+ {"version":3,"file":"auth-services.module.d.ts","sourceRoot":"","sources":["../../src/lib/auth-services.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAQ9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;;;AAGzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;GAGG;AACH,qBAKa,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,GAAG,mBAAmB,CAAC,kBAAkB,CAAC;yCAD9D,kBAAkB;0CAAlB,kBAAkB;0CAAlB,kBAAkB;CAyD9B"}
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { MJAuthBase } from './mjexplorer-auth-base.service';
4
4
  import { AngularAuthProviderFactory } from './AngularAuthProviderFactory';
5
+ import { MJMagicLinkProvider } from './providers/mjexplorer-magic-link-provider.service';
5
6
  import { MJGlobal } from '@memberjunction/global';
6
7
  // Import our generic redirect component
7
8
  import { RedirectComponent } from './redirect.component';
@@ -15,7 +16,15 @@ export { RedirectComponent };
15
16
  export class AuthServicesModule {
16
17
  static forRoot(environment) {
17
18
  const providers = [];
18
- const authType = environment.AUTH_TYPE?.toLowerCase();
19
+ const configuredType = environment.AUTH_TYPE?.toLowerCase();
20
+ // Coexistence resolution: a single Explorer deployment can serve both SSO
21
+ // users and magic-link guests. If a magic-link session token is present for
22
+ // this page load (URL fragment from /redeem, or stored earlier this tab), use
23
+ // the magic-link provider regardless of the configured primary IdP. Otherwise
24
+ // use the configured AUTH_TYPE. This auto-falls-back to the IdP once a guest's
25
+ // token is gone (expired/logged out), so the normal login button works again.
26
+ const magicLinkActive = MJMagicLinkProvider.hasSessionToken();
27
+ const authType = magicLinkActive ? MJMagicLinkProvider.PROVIDER_TYPE : configuredType;
19
28
  if (!authType) {
20
29
  console.error('No AUTH_TYPE specified in environment');
21
30
  return {
@@ -23,6 +32,9 @@ export class AuthServicesModule {
23
32
  providers: []
24
33
  };
25
34
  }
35
+ if (magicLinkActive && authType !== configuredType) {
36
+ console.log(`[Auth] Magic-link session token detected — using the magic-link provider for this load (configured primary: '${configuredType ?? 'none'}').`);
37
+ }
26
38
  // Use the factory to get provider-specific Angular services
27
39
  // This uses the static method on each provider class for extensibility
28
40
  const angularServices = AngularAuthProviderFactory.getProviderAngularServices(authType, environment);
@@ -1 +1 @@
1
- {"version":3,"file":"auth-services.module.js","sourceRoot":"","sources":["../../src/lib/auth-services.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,wCAAwC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;AAEzD,mEAAmE;AACnE,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;GAGG;AAMH,MAAM,OAAO,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,WAAgB;QAE7B,MAAM,SAAS,GAAU,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC;QAEtD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;YACvD,OAAO;gBACL,QAAQ,EAAE,kBAAkB;gBAC5B,SAAS,EAAE,EAAE;aACd,CAAC;QACJ,CAAC;QAED,4DAA4D;QAC5D,uEAAuE;QACvE,MAAM,eAAe,GAAG,0BAA0B,CAAC,0BAA0B,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACrG,SAAS,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;QAEnC,6DAA6D;QAC7D,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CACjE,UAAU,EACV,QAAQ,CACT,CAAC;QACF,MAAM,aAAa,GAAG,YAAY,EAAE,QAAQ,CAAC;QAE7C,IAAI,aAAa,EAAE,CAAC;YAClB,0BAA0B;YAC1B,SAAS,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,aAAa;aACxB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,+CAA+C,QAAQ,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,yBAAyB;QACzB,SAAS,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAE3C,OAAO;YACL,QAAQ,EAAE,kBAAkB;YAC5B,SAAS;SACV,CAAC;IACJ,CAAC;4GA3CU,kBAAkB;4DAAlB,kBAAkB;gEAJnB,YAAY;;iFAIX,kBAAkB;cAL9B,QAAQ;eAAC;gBACR,OAAO,EAAE,CAAC,YAAY,CAAC;gBACvB,YAAY,EAAE,CAAC,iBAAiB,CAAC;gBACjC,OAAO,EAAE,CAAC,iBAAiB,CAAC;aAC7B;;wFACY,kBAAkB,mBAHd,iBAAiB,aADtB,YAAY,aAEZ,iBAAiB","sourcesContent":["import { NgModule, ModuleWithProviders } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MJAuthBase } from './mjexplorer-auth-base.service';\nimport { AngularAuthProviderFactory } from './AngularAuthProviderFactory';\nimport { MJGlobal } from '@memberjunction/global';\n\n// Import our generic redirect component\nimport { RedirectComponent } from './redirect.component';\n\n// Export the generic redirect component for backward compatibility\nexport { RedirectComponent };\n\n/**\n * Extensible authentication module that supports N providers\n * Uses MJGlobal ClassFactory pattern for dynamic provider creation\n */\n@NgModule({\n imports: [CommonModule],\n declarations: [RedirectComponent],\n exports: [RedirectComponent]\n})\nexport class AuthServicesModule {\n static forRoot(environment: any): ModuleWithProviders<AuthServicesModule> {\n\n const providers: any[] = [];\n const authType = environment.AUTH_TYPE?.toLowerCase();\n\n if (!authType) {\n console.error('No AUTH_TYPE specified in environment');\n return {\n ngModule: AuthServicesModule,\n providers: []\n };\n }\n\n // Use the factory to get provider-specific Angular services\n // This uses the static method on each provider class for extensibility\n const angularServices = AngularAuthProviderFactory.getProviderAngularServices(authType, environment);\n providers.push(...angularServices);\n\n // Get the provider class from ClassFactory for extensibility\n const registration = MJGlobal.Instance.ClassFactory.GetRegistration(\n MJAuthBase,\n authType\n );\n const providerClass = registration?.SubClass;\n\n if (providerClass) {\n // Add the provider itself\n providers.push({\n provide: MJAuthBase,\n useClass: providerClass\n });\n } else {\n console.error(`No provider class registered for auth type: ${authType}`);\n }\n\n // Add the factory itself\n providers.push(AngularAuthProviderFactory);\n\n return {\n ngModule: AuthServicesModule,\n providers\n };\n }\n}\n"]}
1
+ {"version":3,"file":"auth-services.module.js","sourceRoot":"","sources":["../../src/lib/auth-services.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,wCAAwC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;AAEzD,mEAAmE;AACnE,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;GAGG;AAMH,MAAM,OAAO,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,WAAgB;QAE7B,MAAM,SAAS,GAAU,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC;QAE5D,0EAA0E;QAC1E,4EAA4E;QAC5E,8EAA8E;QAC9E,8EAA8E;QAC9E,+EAA+E;QAC/E,8EAA8E;QAC9E,MAAM,eAAe,GAAG,mBAAmB,CAAC,eAAe,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC;QAEtF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;YACvD,OAAO;gBACL,QAAQ,EAAE,kBAAkB;gBAC5B,SAAS,EAAE,EAAE;aACd,CAAC;QACJ,CAAC;QAED,IAAI,eAAe,IAAI,QAAQ,KAAK,cAAc,EAAE,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,gHAAgH,cAAc,IAAI,MAAM,KAAK,CAAC,CAAC;QAC7J,CAAC;QAED,4DAA4D;QAC5D,uEAAuE;QACvE,MAAM,eAAe,GAAG,0BAA0B,CAAC,0BAA0B,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACrG,SAAS,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;QAEnC,6DAA6D;QAC7D,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CACjE,UAAU,EACV,QAAQ,CACT,CAAC;QACF,MAAM,aAAa,GAAG,YAAY,EAAE,QAAQ,CAAC;QAE7C,IAAI,aAAa,EAAE,CAAC;YAClB,0BAA0B;YAC1B,SAAS,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,aAAa;aACxB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,+CAA+C,QAAQ,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,yBAAyB;QACzB,SAAS,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAE3C,OAAO;YACL,QAAQ,EAAE,kBAAkB;YAC5B,SAAS;SACV,CAAC;IACJ,CAAC;4GAxDU,kBAAkB;4DAAlB,kBAAkB;gEAJnB,YAAY;;iFAIX,kBAAkB;cAL9B,QAAQ;eAAC;gBACR,OAAO,EAAE,CAAC,YAAY,CAAC;gBACvB,YAAY,EAAE,CAAC,iBAAiB,CAAC;gBACjC,OAAO,EAAE,CAAC,iBAAiB,CAAC;aAC7B;;wFACY,kBAAkB,mBAHd,iBAAiB,aADtB,YAAY,aAEZ,iBAAiB","sourcesContent":["import { NgModule, ModuleWithProviders } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MJAuthBase } from './mjexplorer-auth-base.service';\nimport { AngularAuthProviderFactory } from './AngularAuthProviderFactory';\nimport { MJMagicLinkProvider } from './providers/mjexplorer-magic-link-provider.service';\nimport { MJGlobal } from '@memberjunction/global';\n\n// Import our generic redirect component\nimport { RedirectComponent } from './redirect.component';\n\n// Export the generic redirect component for backward compatibility\nexport { RedirectComponent };\n\n/**\n * Extensible authentication module that supports N providers\n * Uses MJGlobal ClassFactory pattern for dynamic provider creation\n */\n@NgModule({\n imports: [CommonModule],\n declarations: [RedirectComponent],\n exports: [RedirectComponent]\n})\nexport class AuthServicesModule {\n static forRoot(environment: any): ModuleWithProviders<AuthServicesModule> {\n\n const providers: any[] = [];\n const configuredType = environment.AUTH_TYPE?.toLowerCase();\n\n // Coexistence resolution: a single Explorer deployment can serve both SSO\n // users and magic-link guests. If a magic-link session token is present for\n // this page load (URL fragment from /redeem, or stored earlier this tab), use\n // the magic-link provider regardless of the configured primary IdP. Otherwise\n // use the configured AUTH_TYPE. This auto-falls-back to the IdP once a guest's\n // token is gone (expired/logged out), so the normal login button works again.\n const magicLinkActive = MJMagicLinkProvider.hasSessionToken();\n const authType = magicLinkActive ? MJMagicLinkProvider.PROVIDER_TYPE : configuredType;\n\n if (!authType) {\n console.error('No AUTH_TYPE specified in environment');\n return {\n ngModule: AuthServicesModule,\n providers: []\n };\n }\n\n if (magicLinkActive && authType !== configuredType) {\n console.log(`[Auth] Magic-link session token detected — using the magic-link provider for this load (configured primary: '${configuredType ?? 'none'}').`);\n }\n\n // Use the factory to get provider-specific Angular services\n // This uses the static method on each provider class for extensibility\n const angularServices = AngularAuthProviderFactory.getProviderAngularServices(authType, environment);\n providers.push(...angularServices);\n\n // Get the provider class from ClassFactory for extensibility\n const registration = MJGlobal.Instance.ClassFactory.GetRegistration(\n MJAuthBase,\n authType\n );\n const providerClass = registration?.SubClass;\n\n if (providerClass) {\n // Add the provider itself\n providers.push({\n provide: MJAuthBase,\n useClass: providerClass\n });\n } else {\n console.error(`No provider class registered for auth type: ${authType}`);\n }\n\n // Add the factory itself\n providers.push(AngularAuthProviderFactory);\n\n return {\n ngModule: AuthServicesModule,\n providers\n };\n }\n}\n"]}
@@ -254,6 +254,48 @@ export interface StandardAuthError {
254
254
  */
255
255
  userMessage?: string;
256
256
  }
257
+ /**
258
+ * Describes constraints an auth provider places on the session.
259
+ *
260
+ * Most providers (MSAL/Auth0/Okta) impose no constraints and return `null` from
261
+ * `GetSessionScope()`. Constrained providers (e.g. magic-link) can lock the
262
+ * session to a single application so the host UI hides app-switching chrome and
263
+ * keeps the user within their scoped app. This is a UI-confinement signal —
264
+ * data access is still enforced server-side by the user's role/permissions.
265
+ */
266
+ export interface SessionScope {
267
+ /** When set, the session is locked to this single Application (by ID). */
268
+ restrictedToApplicationId?: string;
269
+ /** True when this is a passwordless magic-link session. */
270
+ magicLink?: boolean;
271
+ }
272
+ /**
273
+ * Decoded claims of an MJ-issued magic-link session JWT.
274
+ *
275
+ * Mirrors the claim set minted server-side (`MagicLinkJWTClaims` in MJServer's
276
+ * magic-link module). Declared independently here — NOT re-exported from the
277
+ * server package — so the client has a typed view without a cross-package
278
+ * dependency. All fields optional because the token is decoded without
279
+ * verification (the server validates via JWKS); never trust these for security
280
+ * decisions, only for UI display + app confinement.
281
+ */
282
+ export interface MagicLinkSessionClaims {
283
+ iss?: string;
284
+ aud?: string;
285
+ sub?: string;
286
+ iat?: number;
287
+ exp?: number;
288
+ email?: string;
289
+ given_name?: string;
290
+ family_name?: string;
291
+ name?: string;
292
+ /** The single Application this session is scoped to. */
293
+ mj_app_id?: string;
294
+ /** The restricted role name assigned to this user (informational). */
295
+ mj_role?: string;
296
+ /** Marks the session as magic-link so the host UI can confine it. */
297
+ mj_magic_link?: boolean;
298
+ }
257
299
  /**
258
300
  * Token refresh result
259
301
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth-types.d.ts","sourceRoot":"","sources":["../../src/lib/auth-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;OAYG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,oBAAY,aAAa;IACvB;;;;;;;OAOG;IACH,aAAa,kBAAkB;IAE/B;;;;;;;OAOG;IACH,iBAAiB,sBAAsB;IAEvC;;;;;;;OAOG;IACH,oBAAoB,yBAAyB;IAE7C;;;;;OAKG;IACH,cAAc,mBAAmB;IAEjC;;;;OAIG;IACH,aAAa,kBAAkB;IAE/B;;;;;OAKG;IACH,mBAAmB,wBAAwB;IAE3C;;;;;OAKG;IACH,aAAa,kBAAkB;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,EAAE,aAAa,CAAC;IAEpB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAE1B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IAExB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B"}
1
+ {"version":3,"file":"auth-types.d.ts","sourceRoot":"","sources":["../../src/lib/auth-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-types.js","sourceRoot":"","sources":["../../src/lib/auth-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwHH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAN,IAAY,aA6DX;AA7DD,WAAY,aAAa;IACvB;;;;;;;OAOG;IACH,gDAA+B,CAAA;IAE/B;;;;;;;OAOG;IACH,wDAAuC,CAAA;IAEvC;;;;;;;OAOG;IACH,8DAA6C,CAAA;IAE7C;;;;;OAKG;IACH,kDAAiC,CAAA;IAEjC;;;;OAIG;IACH,gDAA+B,CAAA;IAE/B;;;;;OAKG;IACH,4DAA2C,CAAA;IAE3C;;;;;OAKG;IACH,gDAA+B,CAAA;AACjC,CAAC,EA7DW,aAAa,KAAb,aAAa,QA6DxB","sourcesContent":["/**\n * Standardized authentication types for v3.0.0\n *\n * These types ensure consistent interfaces across all auth providers,\n * eliminating leaky abstractions where consumers needed to know about\n * provider-specific token storage (__raw vs idToken) or claim structures.\n *\n * @module @memberjunction/ng-auth-services\n * @version 3.0.0\n */\n\n/**\n * Standardized user information returned by all auth providers\n *\n * Maps provider-specific claims to a consistent structure.\n * Each provider implements extractUserInfoInternal() to convert their\n * claim format to this standard structure.\n *\n * @example\n * ```typescript\n * const userInfo = await firstValueFrom(this.authBase.getUserInfo());\n * console.log(`Welcome ${userInfo.name}!`);\n * console.log(`Email: ${userInfo.email}`);\n * ```\n */\nexport interface StandardUserInfo {\n /**\n * Unique user identifier from the auth provider\n * (e.g., Auth0: user.sub, MSAL: account.localAccountId)\n */\n id: string;\n\n /**\n * User's email address\n */\n email: string;\n\n /**\n * User's full display name\n */\n name: string;\n\n /**\n * User's given name / first name\n */\n givenName?: string;\n\n /**\n * User's family name / last name\n */\n familyName?: string;\n\n /**\n * Preferred username or handle\n * Often the same as email for most providers\n */\n preferredUsername?: string;\n\n /**\n * URL to user's profile picture (if available)\n */\n pictureUrl?: string;\n\n /**\n * User's locale/language preference\n * (e.g., \"en-US\", \"fr-FR\")\n */\n locale?: string;\n\n /**\n * Email verification status\n * True if the auth provider has verified the user's email\n */\n emailVerified?: boolean;\n}\n\n/**\n * Standardized token information returned by all auth providers\n *\n * Provides access to tokens without exposing provider-specific claim structures.\n * Each provider implements extractTokenInfoInternal() to extract tokens from\n * their specific storage format.\n *\n * @example\n * ```typescript\n * const tokenInfo = await this.authBase.getTokenInfo();\n * console.log(`Token expires at: ${new Date(tokenInfo.expiresAt)}`);\n * ```\n */\nexport interface StandardAuthToken {\n /**\n * The ID token as a JWT string\n *\n * This is what should be sent to the backend GraphQL API in the\n * Authorization header as \"Bearer {idToken}\"\n */\n idToken: string;\n\n /**\n * Access token for calling APIs (if different from ID token)\n *\n * Some providers (like Auth0) use the same token for both authentication\n * and API access. Others (like MSAL) provide separate tokens.\n */\n accessToken?: string;\n\n /**\n * Token expiration timestamp (milliseconds since epoch)\n *\n * Use this to determine if the token needs to be refreshed.\n *\n * @example\n * ```typescript\n * const isExpired = Date.now() >= tokenInfo.expiresAt;\n * if (isExpired) {\n * await this.authBase.refreshToken();\n * }\n * ```\n */\n expiresAt: number;\n\n /**\n * OAuth scopes granted with this token\n *\n * @example [\"openid\", \"profile\", \"email\", \"User.Read\"]\n */\n scopes?: string[];\n}\n\n/**\n * Standardized authentication error types\n *\n * Abstracts provider-specific error names (like \"BrowserAuthError\",\n * \"InteractionRequiredAuthError\") into semantic categories that\n * application code can handle consistently.\n *\n * This eliminates the need for consumers to check provider-specific\n * error properties like `err.name === 'BrowserAuthError'`.\n *\n * @example\n * ```typescript\n * try {\n * await this.authBase.login();\n * } catch (err) {\n * const authError = this.authBase.classifyError(err);\n *\n * switch (authError.type) {\n * case AuthErrorType.TOKEN_EXPIRED:\n * this.showMessage('Session expired. Please log in again.');\n * break;\n * case AuthErrorType.USER_CANCELLED:\n * // User cancelled - don't show error\n * break;\n * default:\n * this.showError(authError.userMessage);\n * }\n * }\n * ```\n */\nexport enum AuthErrorType {\n /**\n * Token has expired - user needs to refresh or re-authenticate\n *\n * Mapped from:\n * - Auth0: \"jwt expired\", \"token expired\"\n * - MSAL: InteractionRequiredAuthError (when token expired)\n * - Okta: \"token_expired\"\n */\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n\n /**\n * No active user session found - user needs to log in\n *\n * Mapped from:\n * - Auth0: \"login_required\", \"no active session\"\n * - MSAL: BrowserAuthError (no accounts)\n * - Okta: \"login_required\"\n */\n NO_ACTIVE_SESSION = 'NO_ACTIVE_SESSION',\n\n /**\n * User interaction required (e.g., consent, MFA)\n *\n * Mapped from:\n * - Auth0: \"consent_required\", \"interaction_required\"\n * - MSAL: InteractionRequiredAuthError\n * - Okta: \"consent_required\"\n */\n INTERACTION_REQUIRED = 'INTERACTION_REQUIRED',\n\n /**\n * User cancelled the authentication flow\n *\n * Typically doesn't require showing an error message to the user,\n * as the cancellation was intentional.\n */\n USER_CANCELLED = 'USER_CANCELLED',\n\n /**\n * Network error communicating with auth provider\n *\n * Could be DNS failure, timeout, or other connectivity issues.\n */\n NETWORK_ERROR = 'NETWORK_ERROR',\n\n /**\n * Invalid configuration or setup error\n *\n * Usually indicates a problem with the auth provider configuration\n * (wrong client ID, invalid redirect URI, etc.)\n */\n CONFIGURATION_ERROR = 'CONFIGURATION_ERROR',\n\n /**\n * Generic/unknown error\n *\n * Used when the error doesn't fit into any other category.\n * The error message and originalError should provide more details.\n */\n UNKNOWN_ERROR = 'UNKNOWN_ERROR'\n}\n\n/**\n * Standardized auth error with categorization\n *\n * Provides both machine-readable error types and human-readable messages.\n * Each provider implements classifyErrorInternal() to map their specific\n * errors to this standard format.\n *\n * @example\n * ```typescript\n * const authError = this.authBase.classifyError(err);\n *\n * // Log for debugging\n * console.error(`Auth error (${authError.type}):`, authError.message);\n *\n * // Show to user\n * this.showErrorMessage(authError.userMessage || authError.message);\n *\n * // Access original error for detailed debugging\n * if (environment.debug) {\n * console.error('Original error:', authError.originalError);\n * }\n * ```\n */\nexport interface StandardAuthError {\n /**\n * Semantic error type for programmatic handling\n *\n * Use this in switch statements or if conditions to handle\n * different error scenarios appropriately.\n */\n type: AuthErrorType;\n\n /**\n * Technical error message\n *\n * Suitable for logging and debugging. May contain technical details\n * not appropriate for end users.\n */\n message: string;\n\n /**\n * Original error from the provider (for debugging)\n *\n * Preserved for detailed error analysis and debugging.\n * Can be any type (Error, object, string, etc.)\n */\n originalError?: unknown;\n\n /**\n * User-friendly error message\n *\n * A message suitable for displaying to end users.\n * Explains the error in plain language and may suggest next steps.\n *\n * @example \"Your session has expired. Please log in again.\"\n */\n userMessage?: string;\n}\n\n/**\n * Token refresh result\n *\n * Returned by refreshToken() to indicate success or failure.\n * Consumers should check the success property before accessing the token.\n *\n * @example\n * ```typescript\n * const result = await this.authBase.refreshToken();\n *\n * if (result.success && result.token) {\n * // Use the refreshed token\n * const newIdToken = result.token.idToken;\n * await this.updateGraphQLClient(newIdToken);\n * } else {\n * // Handle refresh failure\n * console.error('Token refresh failed:', result.error?.message);\n *\n * if (result.error?.type === AuthErrorType.TOKEN_EXPIRED) {\n * // Token is expired and can't be refreshed - need re-login\n * await this.authBase.login();\n * }\n * }\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Whether the refresh was successful\n *\n * If true, the token property will contain the new token.\n * If false, the error property will explain why.\n */\n success: boolean;\n\n /**\n * New token if refresh succeeded\n *\n * Only present when success is true.\n */\n token?: StandardAuthToken;\n\n /**\n * Error if refresh failed\n *\n * Only present when success is false.\n * Contains details about why the refresh failed.\n */\n error?: StandardAuthError;\n}\n\n/**\n * Authentication state snapshot\n *\n * Represents the current authentication state of the application.\n * This is useful for components that need to react to auth state changes.\n *\n * @example\n * ```typescript\n * // Subscribe to complete auth state\n * this.authState$ = this.authBase.getAuthState();\n *\n * this.authState$.subscribe(state => {\n * if (state.isLoading) {\n * this.showLoadingSpinner();\n * } else if (state.isAuthenticated && state.user) {\n * this.showWelcomeMessage(state.user.name);\n * } else if (state.error) {\n * this.showError(state.error.userMessage);\n * }\n * });\n * ```\n */\nexport interface AuthState {\n /**\n * Whether user is currently authenticated\n */\n isAuthenticated: boolean;\n\n /**\n * Current user info (if authenticated)\n *\n * Undefined if not authenticated or still loading.\n */\n user?: StandardUserInfo;\n\n /**\n * Whether auth state is still being determined\n *\n * True during initial authentication check or token refresh.\n * Useful for showing loading spinners.\n */\n isLoading: boolean;\n\n /**\n * Current error (if any)\n *\n * Present if there was an error during authentication or token refresh.\n */\n error?: StandardAuthError;\n}\n"]}
1
+ {"version":3,"file":"auth-types.js","sourceRoot":"","sources":["../../src/lib/auth-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwHH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAN,IAAY,aA6DX;AA7DD,WAAY,aAAa;IACvB;;;;;;;OAOG;IACH,gDAA+B,CAAA;IAE/B;;;;;;;OAOG;IACH,wDAAuC,CAAA;IAEvC;;;;;;;OAOG;IACH,8DAA6C,CAAA;IAE7C;;;;;OAKG;IACH,kDAAiC,CAAA;IAEjC;;;;OAIG;IACH,gDAA+B,CAAA;IAE/B;;;;;OAKG;IACH,4DAA2C,CAAA;IAE3C;;;;;OAKG;IACH,gDAA+B,CAAA;AACjC,CAAC,EA7DW,aAAa,KAAb,aAAa,QA6DxB","sourcesContent":["/**\n * Standardized authentication types for v3.0.0\n *\n * These types ensure consistent interfaces across all auth providers,\n * eliminating leaky abstractions where consumers needed to know about\n * provider-specific token storage (__raw vs idToken) or claim structures.\n *\n * @module @memberjunction/ng-auth-services\n * @version 3.0.0\n */\n\n/**\n * Standardized user information returned by all auth providers\n *\n * Maps provider-specific claims to a consistent structure.\n * Each provider implements extractUserInfoInternal() to convert their\n * claim format to this standard structure.\n *\n * @example\n * ```typescript\n * const userInfo = await firstValueFrom(this.authBase.getUserInfo());\n * console.log(`Welcome ${userInfo.name}!`);\n * console.log(`Email: ${userInfo.email}`);\n * ```\n */\nexport interface StandardUserInfo {\n /**\n * Unique user identifier from the auth provider\n * (e.g., Auth0: user.sub, MSAL: account.localAccountId)\n */\n id: string;\n\n /**\n * User's email address\n */\n email: string;\n\n /**\n * User's full display name\n */\n name: string;\n\n /**\n * User's given name / first name\n */\n givenName?: string;\n\n /**\n * User's family name / last name\n */\n familyName?: string;\n\n /**\n * Preferred username or handle\n * Often the same as email for most providers\n */\n preferredUsername?: string;\n\n /**\n * URL to user's profile picture (if available)\n */\n pictureUrl?: string;\n\n /**\n * User's locale/language preference\n * (e.g., \"en-US\", \"fr-FR\")\n */\n locale?: string;\n\n /**\n * Email verification status\n * True if the auth provider has verified the user's email\n */\n emailVerified?: boolean;\n}\n\n/**\n * Standardized token information returned by all auth providers\n *\n * Provides access to tokens without exposing provider-specific claim structures.\n * Each provider implements extractTokenInfoInternal() to extract tokens from\n * their specific storage format.\n *\n * @example\n * ```typescript\n * const tokenInfo = await this.authBase.getTokenInfo();\n * console.log(`Token expires at: ${new Date(tokenInfo.expiresAt)}`);\n * ```\n */\nexport interface StandardAuthToken {\n /**\n * The ID token as a JWT string\n *\n * This is what should be sent to the backend GraphQL API in the\n * Authorization header as \"Bearer {idToken}\"\n */\n idToken: string;\n\n /**\n * Access token for calling APIs (if different from ID token)\n *\n * Some providers (like Auth0) use the same token for both authentication\n * and API access. Others (like MSAL) provide separate tokens.\n */\n accessToken?: string;\n\n /**\n * Token expiration timestamp (milliseconds since epoch)\n *\n * Use this to determine if the token needs to be refreshed.\n *\n * @example\n * ```typescript\n * const isExpired = Date.now() >= tokenInfo.expiresAt;\n * if (isExpired) {\n * await this.authBase.refreshToken();\n * }\n * ```\n */\n expiresAt: number;\n\n /**\n * OAuth scopes granted with this token\n *\n * @example [\"openid\", \"profile\", \"email\", \"User.Read\"]\n */\n scopes?: string[];\n}\n\n/**\n * Standardized authentication error types\n *\n * Abstracts provider-specific error names (like \"BrowserAuthError\",\n * \"InteractionRequiredAuthError\") into semantic categories that\n * application code can handle consistently.\n *\n * This eliminates the need for consumers to check provider-specific\n * error properties like `err.name === 'BrowserAuthError'`.\n *\n * @example\n * ```typescript\n * try {\n * await this.authBase.login();\n * } catch (err) {\n * const authError = this.authBase.classifyError(err);\n *\n * switch (authError.type) {\n * case AuthErrorType.TOKEN_EXPIRED:\n * this.showMessage('Session expired. Please log in again.');\n * break;\n * case AuthErrorType.USER_CANCELLED:\n * // User cancelled - don't show error\n * break;\n * default:\n * this.showError(authError.userMessage);\n * }\n * }\n * ```\n */\nexport enum AuthErrorType {\n /**\n * Token has expired - user needs to refresh or re-authenticate\n *\n * Mapped from:\n * - Auth0: \"jwt expired\", \"token expired\"\n * - MSAL: InteractionRequiredAuthError (when token expired)\n * - Okta: \"token_expired\"\n */\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n\n /**\n * No active user session found - user needs to log in\n *\n * Mapped from:\n * - Auth0: \"login_required\", \"no active session\"\n * - MSAL: BrowserAuthError (no accounts)\n * - Okta: \"login_required\"\n */\n NO_ACTIVE_SESSION = 'NO_ACTIVE_SESSION',\n\n /**\n * User interaction required (e.g., consent, MFA)\n *\n * Mapped from:\n * - Auth0: \"consent_required\", \"interaction_required\"\n * - MSAL: InteractionRequiredAuthError\n * - Okta: \"consent_required\"\n */\n INTERACTION_REQUIRED = 'INTERACTION_REQUIRED',\n\n /**\n * User cancelled the authentication flow\n *\n * Typically doesn't require showing an error message to the user,\n * as the cancellation was intentional.\n */\n USER_CANCELLED = 'USER_CANCELLED',\n\n /**\n * Network error communicating with auth provider\n *\n * Could be DNS failure, timeout, or other connectivity issues.\n */\n NETWORK_ERROR = 'NETWORK_ERROR',\n\n /**\n * Invalid configuration or setup error\n *\n * Usually indicates a problem with the auth provider configuration\n * (wrong client ID, invalid redirect URI, etc.)\n */\n CONFIGURATION_ERROR = 'CONFIGURATION_ERROR',\n\n /**\n * Generic/unknown error\n *\n * Used when the error doesn't fit into any other category.\n * The error message and originalError should provide more details.\n */\n UNKNOWN_ERROR = 'UNKNOWN_ERROR'\n}\n\n/**\n * Standardized auth error with categorization\n *\n * Provides both machine-readable error types and human-readable messages.\n * Each provider implements classifyErrorInternal() to map their specific\n * errors to this standard format.\n *\n * @example\n * ```typescript\n * const authError = this.authBase.classifyError(err);\n *\n * // Log for debugging\n * console.error(`Auth error (${authError.type}):`, authError.message);\n *\n * // Show to user\n * this.showErrorMessage(authError.userMessage || authError.message);\n *\n * // Access original error for detailed debugging\n * if (environment.debug) {\n * console.error('Original error:', authError.originalError);\n * }\n * ```\n */\nexport interface StandardAuthError {\n /**\n * Semantic error type for programmatic handling\n *\n * Use this in switch statements or if conditions to handle\n * different error scenarios appropriately.\n */\n type: AuthErrorType;\n\n /**\n * Technical error message\n *\n * Suitable for logging and debugging. May contain technical details\n * not appropriate for end users.\n */\n message: string;\n\n /**\n * Original error from the provider (for debugging)\n *\n * Preserved for detailed error analysis and debugging.\n * Can be any type (Error, object, string, etc.)\n */\n originalError?: unknown;\n\n /**\n * User-friendly error message\n *\n * A message suitable for displaying to end users.\n * Explains the error in plain language and may suggest next steps.\n *\n * @example \"Your session has expired. Please log in again.\"\n */\n userMessage?: string;\n}\n\n/**\n * Describes constraints an auth provider places on the session.\n *\n * Most providers (MSAL/Auth0/Okta) impose no constraints and return `null` from\n * `GetSessionScope()`. Constrained providers (e.g. magic-link) can lock the\n * session to a single application so the host UI hides app-switching chrome and\n * keeps the user within their scoped app. This is a UI-confinement signal —\n * data access is still enforced server-side by the user's role/permissions.\n */\nexport interface SessionScope {\n /** When set, the session is locked to this single Application (by ID). */\n restrictedToApplicationId?: string;\n /** True when this is a passwordless magic-link session. */\n magicLink?: boolean;\n}\n\n/**\n * Decoded claims of an MJ-issued magic-link session JWT.\n *\n * Mirrors the claim set minted server-side (`MagicLinkJWTClaims` in MJServer's\n * magic-link module). Declared independently here — NOT re-exported from the\n * server package — so the client has a typed view without a cross-package\n * dependency. All fields optional because the token is decoded without\n * verification (the server validates via JWKS); never trust these for security\n * decisions, only for UI display + app confinement.\n */\nexport interface MagicLinkSessionClaims {\n iss?: string;\n aud?: string;\n sub?: string;\n iat?: number;\n exp?: number;\n email?: string;\n given_name?: string;\n family_name?: string;\n name?: string;\n /** The single Application this session is scoped to. */\n mj_app_id?: string;\n /** The restricted role name assigned to this user (informational). */\n mj_role?: string;\n /** Marks the session as magic-link so the host UI can confine it. */\n mj_magic_link?: boolean;\n}\n\n/**\n * Token refresh result\n *\n * Returned by refreshToken() to indicate success or failure.\n * Consumers should check the success property before accessing the token.\n *\n * @example\n * ```typescript\n * const result = await this.authBase.refreshToken();\n *\n * if (result.success && result.token) {\n * // Use the refreshed token\n * const newIdToken = result.token.idToken;\n * await this.updateGraphQLClient(newIdToken);\n * } else {\n * // Handle refresh failure\n * console.error('Token refresh failed:', result.error?.message);\n *\n * if (result.error?.type === AuthErrorType.TOKEN_EXPIRED) {\n * // Token is expired and can't be refreshed - need re-login\n * await this.authBase.login();\n * }\n * }\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Whether the refresh was successful\n *\n * If true, the token property will contain the new token.\n * If false, the error property will explain why.\n */\n success: boolean;\n\n /**\n * New token if refresh succeeded\n *\n * Only present when success is true.\n */\n token?: StandardAuthToken;\n\n /**\n * Error if refresh failed\n *\n * Only present when success is false.\n * Contains details about why the refresh failed.\n */\n error?: StandardAuthError;\n}\n\n/**\n * Authentication state snapshot\n *\n * Represents the current authentication state of the application.\n * This is useful for components that need to react to auth state changes.\n *\n * @example\n * ```typescript\n * // Subscribe to complete auth state\n * this.authState$ = this.authBase.getAuthState();\n *\n * this.authState$.subscribe(state => {\n * if (state.isLoading) {\n * this.showLoadingSpinner();\n * } else if (state.isAuthenticated && state.user) {\n * this.showWelcomeMessage(state.user.name);\n * } else if (state.error) {\n * this.showError(state.error.userMessage);\n * }\n * });\n * ```\n */\nexport interface AuthState {\n /**\n * Whether user is currently authenticated\n */\n isAuthenticated: boolean;\n\n /**\n * Current user info (if authenticated)\n *\n * Undefined if not authenticated or still loading.\n */\n user?: StandardUserInfo;\n\n /**\n * Whether auth state is still being determined\n *\n * True during initial authentication check or token refresh.\n * Useful for showing loading spinners.\n */\n isLoading: boolean;\n\n /**\n * Current error (if any)\n *\n * Present if there was an error during authentication or token refresh.\n */\n error?: StandardAuthError;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { Observable, BehaviorSubject } from 'rxjs';
2
2
  import { IAngularAuthProvider, AngularAuthProviderConfig } from './IAuthProvider';
3
- import { StandardUserInfo, StandardAuthToken, StandardAuthError, TokenRefreshResult } from './auth-types';
3
+ import { StandardUserInfo, StandardAuthToken, StandardAuthError, TokenRefreshResult, SessionScope } from './auth-types';
4
4
  import * as i0 from "@angular/core";
5
5
  /**
6
6
  * Base class for Angular authentication providers - v3.0.0
@@ -280,6 +280,14 @@ export declare abstract class MJAuthBase implements IAngularAuthProvider {
280
280
  * ```
281
281
  */
282
282
  getProfilePictureUrl(): Promise<string | null>;
283
+ /**
284
+ * Returns any constraints this provider places on the session, or `null` for
285
+ * an unconstrained session. The host shell uses this to confine the UI (e.g.
286
+ * hide app-switching and lock to a single app for magic-link sessions).
287
+ *
288
+ * Default: unconstrained. Constrained providers override this.
289
+ */
290
+ GetSessionScope(): SessionScope | null;
283
291
  /**
284
292
  * Update authentication state
285
293
  *
@@ -1 +1 @@
1
- {"version":3,"file":"mjexplorer-auth-base.service.d.ts","sourceRoot":"","sources":["../../src/lib/mjexplorer-auth-base.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mjexplorer-auth-base.service.d.ts","sourceRoot":"","sources":["../../src/lib/mjexplorer-auth-base.service.ts"],"names":[],"mappings":"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"}
@@ -261,6 +261,16 @@ export class MJAuthBase {
261
261
  async getProfilePictureUrl() {
262
262
  return this.getProfilePictureUrlInternal();
263
263
  }
264
+ /**
265
+ * Returns any constraints this provider places on the session, or `null` for
266
+ * an unconstrained session. The host shell uses this to confine the UI (e.g.
267
+ * hide app-switching and lock to a single app for magic-link sessions).
268
+ *
269
+ * Default: unconstrained. Constrained providers override this.
270
+ */
271
+ GetSessionScope() {
272
+ return null;
273
+ }
264
274
  // ============================================================================
265
275
  // HELPER METHODS (For subclasses to update state)
266
276
  // ============================================================================
@@ -1 +1 @@
1
- {"version":3,"file":"mjexplorer-auth-base.service.js","sourceRoot":"","sources":["../../src/lib/mjexplorer-auth-base.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Observable, BehaviorSubject, from } from 'rxjs';\nimport { IAngularAuthProvider, AngularAuthProviderConfig } from './IAuthProvider';\nimport {\n StandardUserInfo,\n StandardAuthToken,\n StandardAuthError,\n TokenRefreshResult\n} from './auth-types';\n\n/**\n * Base class for Angular authentication providers - v3.0.0\n *\n * Provides common functionality and enforces the provider interface.\n * All concrete providers (MSAL, Auth0, Okta) should extend this class.\n *\n * ## Key Improvements in v3.0:\n * - Proper abstraction - no more leaky provider-specific logic\n * - Standardized types - no more `any` types\n * - Clean token access - no more `__raw || idToken` patterns\n * - Semantic error handling - no more provider-specific error checks\n *\n * ## For Provider Implementers:\n * Extend this class and implement the abstract methods:\n * - `extractIdTokenInternal()` - Extract token from provider storage\n * - `extractTokenInfoInternal()` - Extract full token info\n * - `extractUserInfoInternal()` - Map claims to StandardUserInfo\n * - `refreshTokenInternal()` - Implement refresh mechanism\n * - `classifyErrorInternal()` - Map errors to AuthErrorType\n *\n * @version 3.0.0\n */\n@Injectable()\nexport abstract class MJAuthBase implements IAngularAuthProvider {\n protected config: AngularAuthProviderConfig;\n\n // State management with proper types (no more 'any'!)\n protected isAuthenticated$ = new BehaviorSubject<boolean>(false);\n protected userInfo$ = new BehaviorSubject<StandardUserInfo | null>(null);\n protected userEmail$ = new BehaviorSubject<string>('');\n\n private _initialPath: string | null = null;\n private _initialSearch: string | null = null;\n\n /**\n * Contains the initial path from window.location.pathname before any work was done by auth services\n */\n get initialPath(): string | null {\n return this._initialPath;\n }\n\n /**\n * Contains the initial search/query string from window.location.search before any work was done by auth services\n */\n get initialSearch(): string | null {\n return this._initialSearch;\n }\n\n /**\n * Provider type identifier\n * Must be implemented by concrete providers\n */\n abstract readonly type: string;\n\n constructor(config: AngularAuthProviderConfig) {\n this.config = config;\n this._initialPath = window.location.pathname;\n this._initialSearch = window.location.search;\n }\n\n // ============================================================================\n // ABSTRACT METHODS (Must be implemented by concrete providers)\n // ============================================================================\n\n /**\n * Initialize the provider\n *\n * Subclasses should override to set up provider-specific initialization,\n * handle redirect callbacks, restore sessions, etc.\n */\n abstract initialize(): Promise<void>;\n\n /**\n * Internal login implementation\n *\n * Subclasses implement provider-specific login flow.\n * This is called by the public login() method.\n */\n protected abstract loginInternal(options?: Record<string, unknown>): Promise<void>;\n\n /**\n * Provider-specific logout implementation\n *\n * Subclasses implement provider-specific logout flow (redirect, SDK logout, etc.).\n * The base class `logout()` calls `clearClientCaches()` before invoking this method,\n * so providers do NOT need to handle cache clearing themselves.\n */\n protected abstract logoutInternal(): Promise<void>;\n\n /**\n * Handle OAuth callback\n *\n * Subclasses implement provider-specific callback handling.\n */\n abstract handleCallback(): Promise<void>;\n\n /**\n * Extract ID token from provider-specific storage\n *\n * This is where providers hide their implementation details.\n * - Auth0: Extracts from claims.__raw\n * - MSAL: Extracts from response.idToken\n * - Okta: Extracts from authState.idToken\n *\n * @returns Promise resolving to token string or null if not authenticated\n */\n protected abstract extractIdTokenInternal(): Promise<string | null>;\n\n /**\n * Extract complete token info from provider-specific storage\n *\n * Maps provider-specific token structure to StandardAuthToken.\n *\n * @returns Promise resolving to StandardAuthToken or null if not authenticated\n */\n protected abstract extractTokenInfoInternal(): Promise<StandardAuthToken | null>;\n\n /**\n * Extract user info from provider-specific claims\n *\n * Maps provider-specific claim structure to StandardUserInfo.\n * This is where providers translate their claims (sub, email, name, etc.)\n * into the standard structure.\n *\n * @returns Promise resolving to StandardUserInfo or null if not authenticated\n */\n protected abstract extractUserInfoInternal(): Promise<StandardUserInfo | null>;\n\n /**\n * Refresh token using provider-specific mechanism\n *\n * Implements the provider's token refresh logic using whatever mechanism\n * is appropriate (silent refresh with refresh tokens, iframe-based token\n * acquisition, etc.).\n *\n * Should return success with token if refresh succeeds, or failure with\n * appropriate error type (TOKEN_EXPIRED, INTERACTION_REQUIRED, etc.) if\n * refresh fails.\n *\n * @returns Promise resolving to TokenRefreshResult indicating success/failure\n */\n protected abstract refreshTokenInternal(): Promise<TokenRefreshResult>;\n\n /**\n * Classify provider-specific error into standard error type\n *\n * Maps provider-specific errors to semantic AuthErrorType values.\n * Examines error objects, error codes, and messages to determine the\n * appropriate category (TOKEN_EXPIRED, INTERACTION_REQUIRED, NETWORK_ERROR, etc.).\n *\n * @param error The error to classify\n * @returns StandardAuthError with categorized type and user-friendly message\n */\n protected abstract classifyErrorInternal(error: unknown): StandardAuthError;\n\n /**\n * Get profile picture URL from auth provider\n *\n * Retrieves the user's profile picture using provider-specific mechanisms.\n * Some providers include the URL in user claims, others require API calls\n * to fetch the image.\n *\n * @returns Promise resolving to image URL or null if not available\n */\n protected abstract getProfilePictureUrlInternal(): Promise<string | null>;\n\n /**\n * Handle session expiry when silent refresh fails\n *\n * Called internally when silent token refresh fails with TOKEN_EXPIRED or\n * INTERACTION_REQUIRED errors. Providers that support refresh tokens can\n * implement this as a no-op. Providers that require interactive re-authentication\n * should initiate the appropriate flow (redirect, popup, etc.).\n *\n * Note: If this method redirects the page, it may never return. The app will\n * reload after authentication completes and re-initialize with a fresh token.\n *\n * @returns Promise that resolves if re-auth completed, or never returns if redirected\n */\n protected abstract handleSessionExpiryInternal(): Promise<void>;\n\n // ============================================================================\n // PUBLIC API (Concrete implementations using abstract internals)\n // ============================================================================\n\n /**\n * Public login method with Observable wrapper for backward compatibility\n *\n * Consumers can use either:\n * - `await this.authBase.login()` (Promise)\n * - `this.authBase.login().subscribe()` (Observable)\n */\n login(options?: Record<string, unknown>): Observable<void> {\n return from(this.loginInternal(options));\n }\n\n /**\n * Logout — clears all MJ client-side caches then delegates to the provider.\n *\n * Cache clearing happens universally regardless of auth provider so that a\n * subsequent login as a different user never sees the previous user's data.\n * Providers that need to clear additional caches should override\n * `logoutInternal()` and call `super.logoutInternal()` if applicable.\n */\n async logout(): Promise<void> {\n await this.clearClientCaches();\n await this.logoutInternal();\n }\n\n /**\n * localStorage keys that survive a logout (e.g. UI theme preference).\n *\n * Override this getter in a subclass to preserve additional keys specific\n * to your provider or application.\n */\n protected get preservedLocalStorageKeys(): Set<string> {\n // 'mj-theme' is the unified theme key used by the inline pre-paint\n // script in index.html, MJExplorerAppComponent's login screen, and\n // ThemeService post-login. Preserving across logout means the login\n // screen continues to show the user's last theme without flashing.\n return new Set(['mj-theme']);\n }\n\n /**\n * Clears MJ client-side caches before logout.\n *\n * Removes all localStorage entries except those returned by\n * `preservedLocalStorageKeys`, and deletes the `MJ_Metadata` IndexedDB\n * database so a subsequent user session starts with a clean slate.\n */\n protected async clearClientCaches(): Promise<void> {\n const preserved = this.preservedLocalStorageKeys;\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && !preserved.has(key)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach(key => localStorage.removeItem(key));\n\n await new Promise<void>((resolve) => {\n const req = indexedDB.deleteDatabase('MJ_Metadata');\n req.onsuccess = () => resolve();\n req.onerror = () => resolve();\n req.onblocked = () => resolve();\n });\n }\n\n /**\n * Check if user is authenticated (Observable stream)\n *\n * Returns a reactive stream that emits authentication state changes.\n * Consumers can subscribe to react to login/logout events.\n */\n isAuthenticated(): Observable<boolean> {\n return this.isAuthenticated$.asObservable();\n }\n\n /**\n * Get user info as Observable stream\n *\n * Returns standardized user info, hiding provider-specific claim structures.\n * No more need for consumers to merge claims or check provider-specific fields!\n */\n getUserInfo(): Observable<StandardUserInfo | null> {\n return this.userInfo$.asObservable();\n }\n\n /**\n * Get user email as Observable stream\n */\n getUserEmail(): Observable<string> {\n return this.userEmail$.asObservable();\n }\n\n /**\n * Get ID token string (primary token method)\n *\n * This is the clean abstraction - no provider-specific logic needed!\n * Replaces the old pattern of: `claims?.__raw || claims?.idToken`\n *\n * @example\n * ```typescript\n * const token = await this.authBase.getIdToken();\n * if (token) {\n * setupGraphQLClient(token, apiUrl);\n * }\n * ```\n */\n async getIdToken(): Promise<string | null> {\n return this.extractIdTokenInternal();\n }\n\n /**\n * Get complete token information\n *\n * Returns full token details including expiration and scopes.\n * Use this when you need more than just the token string.\n */\n async getTokenInfo(): Promise<StandardAuthToken | null> {\n return this.extractTokenInfoInternal();\n }\n\n /**\n * Refresh authentication token\n *\n * Attempts to obtain a fresh authentication token using the provider's\n * refresh mechanism. If silent refresh fails due to session expiry, the\n * provider will handle re-authentication automatically (which may involve\n * redirecting to the auth provider's login page).\n *\n * Returns StandardAuthToken on success, or throws on complete failure.\n *\n * IMPORTANT: If the provider requires interactive re-authentication (redirect\n * or popup), this method may never return. The app will reload after\n * authentication completes and re-initialize with a fresh token.\n *\n * @returns Promise resolving to StandardAuthToken or throws on failure\n *\n * @example\n * ```typescript\n * const token = await this.authBase.refreshToken();\n * return token.idToken; // Always succeeds or throws\n * ```\n */\n async refreshToken(): Promise<StandardAuthToken> {\n // Try silent refresh\n const result = await this.refreshTokenInternal();\n\n if (result.success && result.token) {\n // Update state if refresh succeeded\n try {\n const userInfo = await this.extractUserInfoInternal();\n if (userInfo) {\n this.updateUserInfo(userInfo);\n }\n } catch {\n // User info update failed, but token refresh succeeded\n // Don't fail the entire refresh for this\n }\n\n return result.token;\n }\n\n // Silent refresh failed - check if we can handle session expiry\n const errorType = result.error?.type;\n if (errorType === 'TOKEN_EXPIRED' || errorType === 'INTERACTION_REQUIRED') {\n // Let provider handle session expiry (may redirect and never return)\n await this.handleSessionExpiryInternal();\n\n // If we reach here (didn't redirect), retry refresh once\n const retryResult = await this.refreshTokenInternal();\n if (retryResult.success && retryResult.token) {\n // Update state with new token\n try {\n const userInfo = await this.extractUserInfoInternal();\n if (userInfo) {\n this.updateUserInfo(userInfo);\n }\n } catch {\n // Ignore user info update errors\n }\n\n return retryResult.token;\n }\n }\n\n // Complete failure - throw error\n throw new Error(result.error?.userMessage || 'Token refresh failed');\n }\n\n /**\n * Classify an error into standard error type\n *\n * Converts provider-specific errors into semantic categories.\n * Eliminates need for consumers to check error.name or error types.\n *\n * @example\n * ```typescript\n * const authError = this.authBase.classifyError(err);\n * if (authError.type === AuthErrorType.TOKEN_EXPIRED) {\n * this.showMessage(authError.userMessage);\n * }\n * ```\n */\n classifyError(error: unknown): StandardAuthError {\n return this.classifyErrorInternal(error);\n }\n\n /**\n * Get profile picture URL from auth provider\n *\n * Returns the user's profile picture URL if available from the auth provider.\n * This abstracts away provider-specific logic:\n * - Microsoft/MSAL: Fetches from Graph API\n * - Auth0/Okta: Returns from user claims\n *\n * @returns Promise resolving to image URL or null if not available\n *\n * @example\n * ```typescript\n * const pictureUrl = await this.authBase.getProfilePictureUrl();\n * if (pictureUrl) {\n * this.userAvatar = pictureUrl;\n * }\n * ```\n */\n async getProfilePictureUrl(): Promise<string | null> {\n return this.getProfilePictureUrlInternal();\n }\n\n // ============================================================================\n // HELPER METHODS (For subclasses to update state)\n // ============================================================================\n\n /**\n * Update authentication state\n *\n * Subclasses should call this when authentication state changes\n * (after login, logout, session check, etc.)\n */\n protected updateAuthState(isAuthenticated: boolean): void {\n this.isAuthenticated$.next(isAuthenticated);\n }\n\n /**\n * Update user info\n *\n * Subclasses should call this when user info is retrieved or updated.\n * This automatically updates the email stream as well.\n */\n protected updateUserInfo(userInfo: StandardUserInfo | null): void {\n this.userInfo$.next(userInfo);\n\n if (userInfo?.email) {\n this.userEmail$.next(userInfo.email);\n } else {\n this.userEmail$.next('');\n }\n }\n\n // ============================================================================\n // CONFIGURATION & VALIDATION\n // ============================================================================\n\n /**\n * Get required configuration fields\n *\n * Default implementation requires clientId.\n * Subclasses can override to add provider-specific requirements.\n */\n getRequiredConfig(): string[] {\n return ['clientId'];\n }\n\n /**\n * Validate provider configuration\n *\n * Checks that all required fields are present and non-empty.\n * Subclasses can override to add custom validation logic.\n */\n validateConfig(config: Record<string, unknown>): boolean {\n const requiredFields = this.getRequiredConfig();\n return requiredFields.every(field =>\n config[field] !== undefined && config[field] !== ''\n );\n }\n}\n"]}
1
+ {"version":3,"file":"mjexplorer-auth-base.service.js","sourceRoot":"","sources":["../../src/lib/mjexplorer-auth-base.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Observable, BehaviorSubject, from } from 'rxjs';\nimport { IAngularAuthProvider, AngularAuthProviderConfig } from './IAuthProvider';\nimport {\n StandardUserInfo,\n StandardAuthToken,\n StandardAuthError,\n TokenRefreshResult,\n SessionScope\n} from './auth-types';\n\n/**\n * Base class for Angular authentication providers - v3.0.0\n *\n * Provides common functionality and enforces the provider interface.\n * All concrete providers (MSAL, Auth0, Okta) should extend this class.\n *\n * ## Key Improvements in v3.0:\n * - Proper abstraction - no more leaky provider-specific logic\n * - Standardized types - no more `any` types\n * - Clean token access - no more `__raw || idToken` patterns\n * - Semantic error handling - no more provider-specific error checks\n *\n * ## For Provider Implementers:\n * Extend this class and implement the abstract methods:\n * - `extractIdTokenInternal()` - Extract token from provider storage\n * - `extractTokenInfoInternal()` - Extract full token info\n * - `extractUserInfoInternal()` - Map claims to StandardUserInfo\n * - `refreshTokenInternal()` - Implement refresh mechanism\n * - `classifyErrorInternal()` - Map errors to AuthErrorType\n *\n * @version 3.0.0\n */\n@Injectable()\nexport abstract class MJAuthBase implements IAngularAuthProvider {\n protected config: AngularAuthProviderConfig;\n\n // State management with proper types (no more 'any'!)\n protected isAuthenticated$ = new BehaviorSubject<boolean>(false);\n protected userInfo$ = new BehaviorSubject<StandardUserInfo | null>(null);\n protected userEmail$ = new BehaviorSubject<string>('');\n\n private _initialPath: string | null = null;\n private _initialSearch: string | null = null;\n\n /**\n * Contains the initial path from window.location.pathname before any work was done by auth services\n */\n get initialPath(): string | null {\n return this._initialPath;\n }\n\n /**\n * Contains the initial search/query string from window.location.search before any work was done by auth services\n */\n get initialSearch(): string | null {\n return this._initialSearch;\n }\n\n /**\n * Provider type identifier\n * Must be implemented by concrete providers\n */\n abstract readonly type: string;\n\n constructor(config: AngularAuthProviderConfig) {\n this.config = config;\n this._initialPath = window.location.pathname;\n this._initialSearch = window.location.search;\n }\n\n // ============================================================================\n // ABSTRACT METHODS (Must be implemented by concrete providers)\n // ============================================================================\n\n /**\n * Initialize the provider\n *\n * Subclasses should override to set up provider-specific initialization,\n * handle redirect callbacks, restore sessions, etc.\n */\n abstract initialize(): Promise<void>;\n\n /**\n * Internal login implementation\n *\n * Subclasses implement provider-specific login flow.\n * This is called by the public login() method.\n */\n protected abstract loginInternal(options?: Record<string, unknown>): Promise<void>;\n\n /**\n * Provider-specific logout implementation\n *\n * Subclasses implement provider-specific logout flow (redirect, SDK logout, etc.).\n * The base class `logout()` calls `clearClientCaches()` before invoking this method,\n * so providers do NOT need to handle cache clearing themselves.\n */\n protected abstract logoutInternal(): Promise<void>;\n\n /**\n * Handle OAuth callback\n *\n * Subclasses implement provider-specific callback handling.\n */\n abstract handleCallback(): Promise<void>;\n\n /**\n * Extract ID token from provider-specific storage\n *\n * This is where providers hide their implementation details.\n * - Auth0: Extracts from claims.__raw\n * - MSAL: Extracts from response.idToken\n * - Okta: Extracts from authState.idToken\n *\n * @returns Promise resolving to token string or null if not authenticated\n */\n protected abstract extractIdTokenInternal(): Promise<string | null>;\n\n /**\n * Extract complete token info from provider-specific storage\n *\n * Maps provider-specific token structure to StandardAuthToken.\n *\n * @returns Promise resolving to StandardAuthToken or null if not authenticated\n */\n protected abstract extractTokenInfoInternal(): Promise<StandardAuthToken | null>;\n\n /**\n * Extract user info from provider-specific claims\n *\n * Maps provider-specific claim structure to StandardUserInfo.\n * This is where providers translate their claims (sub, email, name, etc.)\n * into the standard structure.\n *\n * @returns Promise resolving to StandardUserInfo or null if not authenticated\n */\n protected abstract extractUserInfoInternal(): Promise<StandardUserInfo | null>;\n\n /**\n * Refresh token using provider-specific mechanism\n *\n * Implements the provider's token refresh logic using whatever mechanism\n * is appropriate (silent refresh with refresh tokens, iframe-based token\n * acquisition, etc.).\n *\n * Should return success with token if refresh succeeds, or failure with\n * appropriate error type (TOKEN_EXPIRED, INTERACTION_REQUIRED, etc.) if\n * refresh fails.\n *\n * @returns Promise resolving to TokenRefreshResult indicating success/failure\n */\n protected abstract refreshTokenInternal(): Promise<TokenRefreshResult>;\n\n /**\n * Classify provider-specific error into standard error type\n *\n * Maps provider-specific errors to semantic AuthErrorType values.\n * Examines error objects, error codes, and messages to determine the\n * appropriate category (TOKEN_EXPIRED, INTERACTION_REQUIRED, NETWORK_ERROR, etc.).\n *\n * @param error The error to classify\n * @returns StandardAuthError with categorized type and user-friendly message\n */\n protected abstract classifyErrorInternal(error: unknown): StandardAuthError;\n\n /**\n * Get profile picture URL from auth provider\n *\n * Retrieves the user's profile picture using provider-specific mechanisms.\n * Some providers include the URL in user claims, others require API calls\n * to fetch the image.\n *\n * @returns Promise resolving to image URL or null if not available\n */\n protected abstract getProfilePictureUrlInternal(): Promise<string | null>;\n\n /**\n * Handle session expiry when silent refresh fails\n *\n * Called internally when silent token refresh fails with TOKEN_EXPIRED or\n * INTERACTION_REQUIRED errors. Providers that support refresh tokens can\n * implement this as a no-op. Providers that require interactive re-authentication\n * should initiate the appropriate flow (redirect, popup, etc.).\n *\n * Note: If this method redirects the page, it may never return. The app will\n * reload after authentication completes and re-initialize with a fresh token.\n *\n * @returns Promise that resolves if re-auth completed, or never returns if redirected\n */\n protected abstract handleSessionExpiryInternal(): Promise<void>;\n\n // ============================================================================\n // PUBLIC API (Concrete implementations using abstract internals)\n // ============================================================================\n\n /**\n * Public login method with Observable wrapper for backward compatibility\n *\n * Consumers can use either:\n * - `await this.authBase.login()` (Promise)\n * - `this.authBase.login().subscribe()` (Observable)\n */\n login(options?: Record<string, unknown>): Observable<void> {\n return from(this.loginInternal(options));\n }\n\n /**\n * Logout — clears all MJ client-side caches then delegates to the provider.\n *\n * Cache clearing happens universally regardless of auth provider so that a\n * subsequent login as a different user never sees the previous user's data.\n * Providers that need to clear additional caches should override\n * `logoutInternal()` and call `super.logoutInternal()` if applicable.\n */\n async logout(): Promise<void> {\n await this.clearClientCaches();\n await this.logoutInternal();\n }\n\n /**\n * localStorage keys that survive a logout (e.g. UI theme preference).\n *\n * Override this getter in a subclass to preserve additional keys specific\n * to your provider or application.\n */\n protected get preservedLocalStorageKeys(): Set<string> {\n // 'mj-theme' is the unified theme key used by the inline pre-paint\n // script in index.html, MJExplorerAppComponent's login screen, and\n // ThemeService post-login. Preserving across logout means the login\n // screen continues to show the user's last theme without flashing.\n return new Set(['mj-theme']);\n }\n\n /**\n * Clears MJ client-side caches before logout.\n *\n * Removes all localStorage entries except those returned by\n * `preservedLocalStorageKeys`, and deletes the `MJ_Metadata` IndexedDB\n * database so a subsequent user session starts with a clean slate.\n */\n protected async clearClientCaches(): Promise<void> {\n const preserved = this.preservedLocalStorageKeys;\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && !preserved.has(key)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach(key => localStorage.removeItem(key));\n\n await new Promise<void>((resolve) => {\n const req = indexedDB.deleteDatabase('MJ_Metadata');\n req.onsuccess = () => resolve();\n req.onerror = () => resolve();\n req.onblocked = () => resolve();\n });\n }\n\n /**\n * Check if user is authenticated (Observable stream)\n *\n * Returns a reactive stream that emits authentication state changes.\n * Consumers can subscribe to react to login/logout events.\n */\n isAuthenticated(): Observable<boolean> {\n return this.isAuthenticated$.asObservable();\n }\n\n /**\n * Get user info as Observable stream\n *\n * Returns standardized user info, hiding provider-specific claim structures.\n * No more need for consumers to merge claims or check provider-specific fields!\n */\n getUserInfo(): Observable<StandardUserInfo | null> {\n return this.userInfo$.asObservable();\n }\n\n /**\n * Get user email as Observable stream\n */\n getUserEmail(): Observable<string> {\n return this.userEmail$.asObservable();\n }\n\n /**\n * Get ID token string (primary token method)\n *\n * This is the clean abstraction - no provider-specific logic needed!\n * Replaces the old pattern of: `claims?.__raw || claims?.idToken`\n *\n * @example\n * ```typescript\n * const token = await this.authBase.getIdToken();\n * if (token) {\n * setupGraphQLClient(token, apiUrl);\n * }\n * ```\n */\n async getIdToken(): Promise<string | null> {\n return this.extractIdTokenInternal();\n }\n\n /**\n * Get complete token information\n *\n * Returns full token details including expiration and scopes.\n * Use this when you need more than just the token string.\n */\n async getTokenInfo(): Promise<StandardAuthToken | null> {\n return this.extractTokenInfoInternal();\n }\n\n /**\n * Refresh authentication token\n *\n * Attempts to obtain a fresh authentication token using the provider's\n * refresh mechanism. If silent refresh fails due to session expiry, the\n * provider will handle re-authentication automatically (which may involve\n * redirecting to the auth provider's login page).\n *\n * Returns StandardAuthToken on success, or throws on complete failure.\n *\n * IMPORTANT: If the provider requires interactive re-authentication (redirect\n * or popup), this method may never return. The app will reload after\n * authentication completes and re-initialize with a fresh token.\n *\n * @returns Promise resolving to StandardAuthToken or throws on failure\n *\n * @example\n * ```typescript\n * const token = await this.authBase.refreshToken();\n * return token.idToken; // Always succeeds or throws\n * ```\n */\n async refreshToken(): Promise<StandardAuthToken> {\n // Try silent refresh\n const result = await this.refreshTokenInternal();\n\n if (result.success && result.token) {\n // Update state if refresh succeeded\n try {\n const userInfo = await this.extractUserInfoInternal();\n if (userInfo) {\n this.updateUserInfo(userInfo);\n }\n } catch {\n // User info update failed, but token refresh succeeded\n // Don't fail the entire refresh for this\n }\n\n return result.token;\n }\n\n // Silent refresh failed - check if we can handle session expiry\n const errorType = result.error?.type;\n if (errorType === 'TOKEN_EXPIRED' || errorType === 'INTERACTION_REQUIRED') {\n // Let provider handle session expiry (may redirect and never return)\n await this.handleSessionExpiryInternal();\n\n // If we reach here (didn't redirect), retry refresh once\n const retryResult = await this.refreshTokenInternal();\n if (retryResult.success && retryResult.token) {\n // Update state with new token\n try {\n const userInfo = await this.extractUserInfoInternal();\n if (userInfo) {\n this.updateUserInfo(userInfo);\n }\n } catch {\n // Ignore user info update errors\n }\n\n return retryResult.token;\n }\n }\n\n // Complete failure - throw error\n throw new Error(result.error?.userMessage || 'Token refresh failed');\n }\n\n /**\n * Classify an error into standard error type\n *\n * Converts provider-specific errors into semantic categories.\n * Eliminates need for consumers to check error.name or error types.\n *\n * @example\n * ```typescript\n * const authError = this.authBase.classifyError(err);\n * if (authError.type === AuthErrorType.TOKEN_EXPIRED) {\n * this.showMessage(authError.userMessage);\n * }\n * ```\n */\n classifyError(error: unknown): StandardAuthError {\n return this.classifyErrorInternal(error);\n }\n\n /**\n * Get profile picture URL from auth provider\n *\n * Returns the user's profile picture URL if available from the auth provider.\n * This abstracts away provider-specific logic:\n * - Microsoft/MSAL: Fetches from Graph API\n * - Auth0/Okta: Returns from user claims\n *\n * @returns Promise resolving to image URL or null if not available\n *\n * @example\n * ```typescript\n * const pictureUrl = await this.authBase.getProfilePictureUrl();\n * if (pictureUrl) {\n * this.userAvatar = pictureUrl;\n * }\n * ```\n */\n async getProfilePictureUrl(): Promise<string | null> {\n return this.getProfilePictureUrlInternal();\n }\n\n /**\n * Returns any constraints this provider places on the session, or `null` for\n * an unconstrained session. The host shell uses this to confine the UI (e.g.\n * hide app-switching and lock to a single app for magic-link sessions).\n *\n * Default: unconstrained. Constrained providers override this.\n */\n public GetSessionScope(): SessionScope | null {\n return null;\n }\n\n // ============================================================================\n // HELPER METHODS (For subclasses to update state)\n // ============================================================================\n\n /**\n * Update authentication state\n *\n * Subclasses should call this when authentication state changes\n * (after login, logout, session check, etc.)\n */\n protected updateAuthState(isAuthenticated: boolean): void {\n this.isAuthenticated$.next(isAuthenticated);\n }\n\n /**\n * Update user info\n *\n * Subclasses should call this when user info is retrieved or updated.\n * This automatically updates the email stream as well.\n */\n protected updateUserInfo(userInfo: StandardUserInfo | null): void {\n this.userInfo$.next(userInfo);\n\n if (userInfo?.email) {\n this.userEmail$.next(userInfo.email);\n } else {\n this.userEmail$.next('');\n }\n }\n\n // ============================================================================\n // CONFIGURATION & VALIDATION\n // ============================================================================\n\n /**\n * Get required configuration fields\n *\n * Default implementation requires clientId.\n * Subclasses can override to add provider-specific requirements.\n */\n getRequiredConfig(): string[] {\n return ['clientId'];\n }\n\n /**\n * Validate provider configuration\n *\n * Checks that all required fields are present and non-empty.\n * Subclasses can override to add custom validation logic.\n */\n validateConfig(config: Record<string, unknown>): boolean {\n const requiredFields = this.getRequiredConfig();\n return requiredFields.every(field =>\n config[field] !== undefined && config[field] !== ''\n );\n }\n}\n"]}
@@ -0,0 +1,70 @@
1
+ import { MJAuthBase } from '../mjexplorer-auth-base.service';
2
+ import { StandardUserInfo, StandardAuthToken, StandardAuthError, TokenRefreshResult, SessionScope } from '../auth-types';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Client-side auth provider for MemberJunction-issued magic-link sessions.
6
+ *
7
+ * Unlike MSAL/Auth0, there is no interactive login and no SDK: the session
8
+ * token (minted server-side when the user redeemed their invite) arrives in the
9
+ * URL fragment — `#token=<jwt>` — when `/magic-link/redeem` redirects the
10
+ * browser to Explorer. This provider extracts it, stashes it in sessionStorage
11
+ * (per-tab, so it dies with the tab), decodes the claims for display, and hands
12
+ * the token to the GraphQL client via `getIdToken()`. The server validates it
13
+ * through the standard JWKS path.
14
+ *
15
+ * Set `AUTH_TYPE: 'magic-link'` in the Explorer environment to use this
16
+ * provider. There are no refresh tokens — when the session expires the user
17
+ * must redeem a fresh link.
18
+ */
19
+ export declare class MJMagicLinkProvider extends MJAuthBase {
20
+ static readonly PROVIDER_TYPE = "magic-link";
21
+ readonly type = "magic-link";
22
+ private token;
23
+ private claims;
24
+ constructor();
25
+ /**
26
+ * True if a magic-link session token is present for this page load — either
27
+ * arriving in the URL fragment (`#token=<jwt>`, from the redeem redirect) or
28
+ * already stashed in sessionStorage from earlier in this tab.
29
+ *
30
+ * Used at module-config time (`AuthServicesModule.forRoot`) to auto-select the
31
+ * magic-link provider even when `AUTH_TYPE` names a different primary IdP, so a
32
+ * single Explorer deployment can serve both SSO users and magic-link guests.
33
+ * Falls back cleanly to the primary IdP when no token is present (e.g. after a
34
+ * guest's session expires or logs out).
35
+ */
36
+ static hasSessionToken(): boolean;
37
+ initialize(): Promise<void>;
38
+ protected loginInternal(): Promise<void>;
39
+ protected logoutInternal(): Promise<void>;
40
+ handleCallback(): Promise<void>;
41
+ protected extractIdTokenInternal(): Promise<string | null>;
42
+ protected extractTokenInfoInternal(): Promise<StandardAuthToken | null>;
43
+ protected extractUserInfoInternal(): Promise<StandardUserInfo | null>;
44
+ protected refreshTokenInternal(): Promise<TokenRefreshResult>;
45
+ protected classifyErrorInternal(error: unknown): StandardAuthError;
46
+ protected getProfilePictureUrlInternal(): Promise<string | null>;
47
+ protected handleSessionExpiryInternal(): Promise<void>;
48
+ /**
49
+ * Magic-link sessions are locked to the single app the invite scoped to
50
+ * (`mj_app_id`), so the shell hides app-switching and keeps the user there.
51
+ */
52
+ GetSessionScope(): SessionScope | null;
53
+ getRequiredConfig(): string[];
54
+ validateConfig(): boolean;
55
+ /** Reads `token` from the URL fragment (`#token=...`). */
56
+ private readTokenFromHash;
57
+ /** Removes the token from the address bar without reloading. */
58
+ private stripHash;
59
+ /**
60
+ * Decodes a JWT payload (no verification — the server validates via JWKS).
61
+ * UTF-8-safe: external users routinely have non-ASCII names, which a plain
62
+ * `atob` would mangle. Also restores base64url padding `atob` needs.
63
+ */
64
+ private decode;
65
+ private isExpired;
66
+ private toUserInfo;
67
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJMagicLinkProvider, never>;
68
+ static ɵprov: i0.ɵɵInjectableDeclaration<MJMagicLinkProvider>;
69
+ }
70
+ //# sourceMappingURL=mjexplorer-magic-link-provider.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mjexplorer-magic-link-provider.service.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/mjexplorer-magic-link-provider.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EAEjB,kBAAkB,EAClB,YAAY,EAEb,MAAM,eAAe,CAAC;;AAKvB;;;;;;;;;;;;;;GAcG;AACH,qBAEa,mBAAoB,SAAQ,UAAU;IACjD,MAAM,CAAC,QAAQ,CAAC,aAAa,gBAAgB;IAC7C,QAAQ,CAAC,IAAI,gBAAqC;IAElD,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,MAAM,CAAuC;;IAMrD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,eAAe,IAAI,OAAO;IAgB3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;cAiCjB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;cAK9B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAQzC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;cAIrB,sBAAsB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;cAIhD,wBAAwB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;cAU7D,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;cAI3D,oBAAoB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAiBnE,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB;cAKlD,4BAA4B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;cAItD,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5D;;;OAGG;IACM,eAAe,IAAI,YAAY,GAAG,IAAI;IAQtC,iBAAiB,IAAI,MAAM,EAAE;IAI7B,cAAc,IAAI,OAAO;IAMlC,0DAA0D;IAC1D,OAAO,CAAC,iBAAiB;IAQzB,gEAAgE;IAChE,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;yCA5MP,mBAAmB;6CAAnB,mBAAmB;CA2N/B"}
@@ -0,0 +1,233 @@
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 MJMagicLinkProvider_1;
8
+ import { Injectable } from '@angular/core';
9
+ import { RegisterClass } from '@memberjunction/global';
10
+ import { MJAuthBase } from '../mjexplorer-auth-base.service';
11
+ import { AuthErrorType, } from '../auth-types';
12
+ import * as i0 from "@angular/core";
13
+ /** sessionStorage key holding the magic-link session token for this tab. */
14
+ const TOKEN_STORAGE_KEY = 'mj_magic_link_token';
15
+ /**
16
+ * Client-side auth provider for MemberJunction-issued magic-link sessions.
17
+ *
18
+ * Unlike MSAL/Auth0, there is no interactive login and no SDK: the session
19
+ * token (minted server-side when the user redeemed their invite) arrives in the
20
+ * URL fragment — `#token=<jwt>` — when `/magic-link/redeem` redirects the
21
+ * browser to Explorer. This provider extracts it, stashes it in sessionStorage
22
+ * (per-tab, so it dies with the tab), decodes the claims for display, and hands
23
+ * the token to the GraphQL client via `getIdToken()`. The server validates it
24
+ * through the standard JWKS path.
25
+ *
26
+ * Set `AUTH_TYPE: 'magic-link'` in the Explorer environment to use this
27
+ * provider. There are no refresh tokens — when the session expires the user
28
+ * must redeem a fresh link.
29
+ */
30
+ let MJMagicLinkProvider = class MJMagicLinkProvider extends MJAuthBase {
31
+ static { MJMagicLinkProvider_1 = this; }
32
+ static PROVIDER_TYPE = 'magic-link';
33
+ type = MJMagicLinkProvider_1.PROVIDER_TYPE;
34
+ token = null;
35
+ claims = null;
36
+ constructor() {
37
+ super({ name: MJMagicLinkProvider_1.PROVIDER_TYPE, type: MJMagicLinkProvider_1.PROVIDER_TYPE });
38
+ }
39
+ /**
40
+ * True if a magic-link session token is present for this page load — either
41
+ * arriving in the URL fragment (`#token=<jwt>`, from the redeem redirect) or
42
+ * already stashed in sessionStorage from earlier in this tab.
43
+ *
44
+ * Used at module-config time (`AuthServicesModule.forRoot`) to auto-select the
45
+ * magic-link provider even when `AUTH_TYPE` names a different primary IdP, so a
46
+ * single Explorer deployment can serve both SSO users and magic-link guests.
47
+ * Falls back cleanly to the primary IdP when no token is present (e.g. after a
48
+ * guest's session expires or logs out).
49
+ */
50
+ static hasSessionToken() {
51
+ if (typeof window === 'undefined') {
52
+ return false;
53
+ }
54
+ try {
55
+ const hash = window.location?.hash ?? '';
56
+ const fragment = hash.startsWith('#') ? hash.slice(1) : hash;
57
+ if (new URLSearchParams(fragment).get('token')) {
58
+ return true;
59
+ }
60
+ return !!window.sessionStorage?.getItem(TOKEN_STORAGE_KEY);
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ async initialize() {
67
+ // Token may arrive in the URL fragment from the redeem redirect, or already
68
+ // be stored from earlier in this tab's session.
69
+ const fromHash = this.readTokenFromHash();
70
+ const stored = sessionStorage.getItem(TOKEN_STORAGE_KEY);
71
+ const token = fromHash ?? stored;
72
+ if (!token) {
73
+ this.updateAuthState(false);
74
+ return;
75
+ }
76
+ this.token = token;
77
+ this.claims = this.decode(token);
78
+ if (fromHash) {
79
+ sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
80
+ this.stripHash(); // remove the token from the visible URL
81
+ }
82
+ if (this.isExpired()) {
83
+ // Stale token — treat as logged out so the user is prompted for a fresh link.
84
+ sessionStorage.removeItem(TOKEN_STORAGE_KEY);
85
+ this.token = null;
86
+ this.claims = null;
87
+ this.updateAuthState(false);
88
+ return;
89
+ }
90
+ this.updateUserInfo(this.toUserInfo(this.claims));
91
+ this.updateAuthState(true);
92
+ }
93
+ async loginInternal() {
94
+ // No interactive login path — access is only via a magic-link redemption URL.
95
+ this.updateAuthState(false);
96
+ }
97
+ async logoutInternal() {
98
+ sessionStorage.removeItem(TOKEN_STORAGE_KEY);
99
+ this.token = null;
100
+ this.claims = null;
101
+ this.updateUserInfo(null);
102
+ this.updateAuthState(false);
103
+ }
104
+ async handleCallback() {
105
+ // No OAuth callback for magic links.
106
+ }
107
+ async extractIdTokenInternal() {
108
+ return this.token ?? sessionStorage.getItem(TOKEN_STORAGE_KEY);
109
+ }
110
+ async extractTokenInfoInternal() {
111
+ const idToken = await this.extractIdTokenInternal();
112
+ if (!idToken) {
113
+ return null;
114
+ }
115
+ const claims = this.claims ?? this.decode(idToken);
116
+ const exp = claims?.exp;
117
+ return { idToken, expiresAt: exp ? exp * 1000 : 0 };
118
+ }
119
+ async extractUserInfoInternal() {
120
+ return this.toUserInfo(this.claims);
121
+ }
122
+ async refreshTokenInternal() {
123
+ // No refresh tokens for magic-link sessions. Hand back the current token
124
+ // while it's valid; once expired, signal TOKEN_EXPIRED.
125
+ const token = await this.extractTokenInfoInternal();
126
+ if (token && !this.isExpired()) {
127
+ return { success: true, token };
128
+ }
129
+ return {
130
+ success: false,
131
+ error: {
132
+ type: AuthErrorType.TOKEN_EXPIRED,
133
+ message: 'Magic-link session expired',
134
+ userMessage: 'Your access link has expired. Please request a new one.',
135
+ },
136
+ };
137
+ }
138
+ classifyErrorInternal(error) {
139
+ const message = error?.message ?? 'Unknown error';
140
+ return { type: AuthErrorType.UNKNOWN_ERROR, message, originalError: error };
141
+ }
142
+ async getProfilePictureUrlInternal() {
143
+ return null;
144
+ }
145
+ async handleSessionExpiryInternal() {
146
+ // No interactive re-auth — the user must redeem a fresh link.
147
+ }
148
+ /**
149
+ * Magic-link sessions are locked to the single app the invite scoped to
150
+ * (`mj_app_id`), so the shell hides app-switching and keeps the user there.
151
+ */
152
+ GetSessionScope() {
153
+ const appId = this.claims?.mj_app_id;
154
+ if (this.claims?.mj_magic_link === true && appId) {
155
+ return { restrictedToApplicationId: appId, magicLink: true };
156
+ }
157
+ return null;
158
+ }
159
+ getRequiredConfig() {
160
+ return [];
161
+ }
162
+ validateConfig() {
163
+ return true;
164
+ }
165
+ // ── helpers ────────────────────────────────────────────────────────────────
166
+ /** Reads `token` from the URL fragment (`#token=...`). */
167
+ readTokenFromHash() {
168
+ const hash = window.location.hash;
169
+ if (!hash) {
170
+ return null;
171
+ }
172
+ return new URLSearchParams(hash.replace(/^#/, '')).get('token');
173
+ }
174
+ /** Removes the token from the address bar without reloading. */
175
+ stripHash() {
176
+ history.replaceState(null, '', window.location.pathname + window.location.search);
177
+ }
178
+ /**
179
+ * Decodes a JWT payload (no verification — the server validates via JWKS).
180
+ * UTF-8-safe: external users routinely have non-ASCII names, which a plain
181
+ * `atob` would mangle. Also restores base64url padding `atob` needs.
182
+ */
183
+ decode(token) {
184
+ try {
185
+ const segment = token.split('.')[1];
186
+ if (!segment) {
187
+ return null;
188
+ }
189
+ let b64 = segment.replace(/-/g, '+').replace(/_/g, '/');
190
+ b64 += '='.repeat((4 - (b64.length % 4)) % 4);
191
+ const json = decodeURIComponent(atob(b64)
192
+ .split('')
193
+ .map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))
194
+ .join(''));
195
+ return JSON.parse(json);
196
+ }
197
+ catch (e) {
198
+ // A malformed token must not be silently indistinguishable from "no token".
199
+ console.error('[MagicLink] Failed to decode session token:', e);
200
+ return null;
201
+ }
202
+ }
203
+ isExpired() {
204
+ const exp = this.claims?.exp;
205
+ return !!exp && Date.now() >= exp * 1000;
206
+ }
207
+ toUserInfo(claims) {
208
+ if (!claims) {
209
+ return null;
210
+ }
211
+ const email = claims.email || '';
212
+ return {
213
+ id: claims.sub || email,
214
+ email,
215
+ name: claims.name || email,
216
+ givenName: claims.given_name,
217
+ familyName: claims.family_name,
218
+ preferredUsername: email,
219
+ emailVerified: true,
220
+ };
221
+ }
222
+ static ɵfac = function MJMagicLinkProvider_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJMagicLinkProvider)(); };
223
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MJMagicLinkProvider, factory: MJMagicLinkProvider.ɵfac, providedIn: 'root' });
224
+ };
225
+ MJMagicLinkProvider = MJMagicLinkProvider_1 = __decorate([
226
+ RegisterClass(MJAuthBase, 'magic-link')
227
+ ], MJMagicLinkProvider);
228
+ export { MJMagicLinkProvider };
229
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJMagicLinkProvider, [{
230
+ type: Injectable,
231
+ args: [{ providedIn: 'root' }]
232
+ }], () => [], null); })();
233
+ //# sourceMappingURL=mjexplorer-magic-link-provider.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mjexplorer-magic-link-provider.service.js","sourceRoot":"","sources":["../../../src/lib/providers/mjexplorer-magic-link-provider.service.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAIL,aAAa,GAId,MAAM,eAAe,CAAC;;AAEvB,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AAGI,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,UAAU;;IACjD,MAAM,CAAU,aAAa,GAAG,YAAY,CAAC;IACpC,IAAI,GAAG,qBAAmB,CAAC,aAAa,CAAC;IAE1C,KAAK,GAAkB,IAAI,CAAC;IAC5B,MAAM,GAAkC,IAAI,CAAC;IAErD;QACE,KAAK,CAAC,EAAE,IAAI,EAAE,qBAAmB,CAAC,aAAa,EAAE,IAAI,EAAE,qBAAmB,CAAC,aAAa,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,eAAe;QACpB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;YACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7D,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/C,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU;QACd,4EAA4E;QAC5E,gDAAgD;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,QAAQ,IAAI,MAAM,CAAC;QAEjC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEjC,IAAI,QAAQ,EAAE,CAAC;YACb,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YACjD,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,wCAAwC;QAC5D,CAAC;QAED,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACrB,8EAA8E;YAC9E,cAAc,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAES,KAAK,CAAC,aAAa;QAC3B,8EAA8E;QAC9E,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAES,KAAK,CAAC,cAAc;QAC5B,cAAc,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,qCAAqC;IACvC,CAAC;IAES,KAAK,CAAC,sBAAsB;QACpC,OAAO,IAAI,CAAC,KAAK,IAAI,cAAc,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACjE,CAAC;IAES,KAAK,CAAC,wBAAwB;QACtC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,sBAAsB,EAAE,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,CAAC;QACxB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IAES,KAAK,CAAC,uBAAuB;QACrC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAES,KAAK,CAAC,oBAAoB;QAClC,yEAAyE;QACzE,wDAAwD;QACxD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;QACpD,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YAC/B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa,CAAC,aAAa;gBACjC,OAAO,EAAE,4BAA4B;gBACrC,WAAW,EAAE,yDAAyD;aACvE;SACF,CAAC;IACJ,CAAC;IAES,qBAAqB,CAAC,KAAc;QAC5C,MAAM,OAAO,GAAI,KAA8B,EAAE,OAAO,IAAI,eAAe,CAAC;QAC5E,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAC9E,CAAC;IAES,KAAK,CAAC,4BAA4B;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAES,KAAK,CAAC,2BAA2B;QACzC,8DAA8D;IAChE,CAAC;IAED;;;OAGG;IACM,eAAe;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;QACrC,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,IAAI,KAAK,EAAE,CAAC;YACjD,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEQ,iBAAiB;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IAEQ,cAAc;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8EAA8E;IAE9E,0DAA0D;IAClD,iBAAiB;QACvB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC;IAED,gEAAgE;IACxD,SAAS;QACf,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpF,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAa;QAC1B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACxD,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAG,kBAAkB,CAC7B,IAAI,CAAC,GAAG,CAAC;iBACN,KAAK,CAAC,EAAE,CAAC;iBACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;iBAC/D,IAAI,CAAC,EAAE,CAAC,CACZ,CAAC;YACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;QACpD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,4EAA4E;YAC5E,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,CAAC,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,SAAS;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;QAC7B,OAAO,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,GAAG,IAAI,CAAC;IAC3C,CAAC;IAEO,UAAU,CAAC,MAAqC;QACtD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,GAAG,IAAI,KAAK;YACvB,KAAK;YACL,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;YAC1B,SAAS,EAAE,MAAM,CAAC,UAAU;YAC5B,UAAU,EAAE,MAAM,CAAC,WAAW;YAC9B,iBAAiB,EAAE,KAAK;YACxB,aAAa,EAAE,IAAI;SACpB,CAAC;IACJ,CAAC;6GA1NU,mBAAmB;gEAAnB,mBAAmB,WAAnB,mBAAmB,mBAFN,MAAM;;AAEnB,mBAAmB;IAD/B,aAAa,CAAC,UAAU,EAAE,YAAY,CAAC;GAC3B,mBAAmB,CA2N/B;;iFA3NY,mBAAmB;cAF/B,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { MJAuthBase } from '../mjexplorer-auth-base.service';\nimport {\n StandardUserInfo,\n StandardAuthToken,\n StandardAuthError,\n AuthErrorType,\n TokenRefreshResult,\n SessionScope,\n MagicLinkSessionClaims,\n} from '../auth-types';\n\n/** sessionStorage key holding the magic-link session token for this tab. */\nconst TOKEN_STORAGE_KEY = 'mj_magic_link_token';\n\n/**\n * Client-side auth provider for MemberJunction-issued magic-link sessions.\n *\n * Unlike MSAL/Auth0, there is no interactive login and no SDK: the session\n * token (minted server-side when the user redeemed their invite) arrives in the\n * URL fragment — `#token=<jwt>` — when `/magic-link/redeem` redirects the\n * browser to Explorer. This provider extracts it, stashes it in sessionStorage\n * (per-tab, so it dies with the tab), decodes the claims for display, and hands\n * the token to the GraphQL client via `getIdToken()`. The server validates it\n * through the standard JWKS path.\n *\n * Set `AUTH_TYPE: 'magic-link'` in the Explorer environment to use this\n * provider. There are no refresh tokens — when the session expires the user\n * must redeem a fresh link.\n */\n@Injectable({ providedIn: 'root' })\n@RegisterClass(MJAuthBase, 'magic-link')\nexport class MJMagicLinkProvider extends MJAuthBase {\n static readonly PROVIDER_TYPE = 'magic-link';\n readonly type = MJMagicLinkProvider.PROVIDER_TYPE;\n\n private token: string | null = null;\n private claims: MagicLinkSessionClaims | null = null;\n\n constructor() {\n super({ name: MJMagicLinkProvider.PROVIDER_TYPE, type: MJMagicLinkProvider.PROVIDER_TYPE });\n }\n\n /**\n * True if a magic-link session token is present for this page load — either\n * arriving in the URL fragment (`#token=<jwt>`, from the redeem redirect) or\n * already stashed in sessionStorage from earlier in this tab.\n *\n * Used at module-config time (`AuthServicesModule.forRoot`) to auto-select the\n * magic-link provider even when `AUTH_TYPE` names a different primary IdP, so a\n * single Explorer deployment can serve both SSO users and magic-link guests.\n * Falls back cleanly to the primary IdP when no token is present (e.g. after a\n * guest's session expires or logs out).\n */\n static hasSessionToken(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n try {\n const hash = window.location?.hash ?? '';\n const fragment = hash.startsWith('#') ? hash.slice(1) : hash;\n if (new URLSearchParams(fragment).get('token')) {\n return true;\n }\n return !!window.sessionStorage?.getItem(TOKEN_STORAGE_KEY);\n } catch {\n return false;\n }\n }\n\n async initialize(): Promise<void> {\n // Token may arrive in the URL fragment from the redeem redirect, or already\n // be stored from earlier in this tab's session.\n const fromHash = this.readTokenFromHash();\n const stored = sessionStorage.getItem(TOKEN_STORAGE_KEY);\n const token = fromHash ?? stored;\n\n if (!token) {\n this.updateAuthState(false);\n return;\n }\n\n this.token = token;\n this.claims = this.decode(token);\n\n if (fromHash) {\n sessionStorage.setItem(TOKEN_STORAGE_KEY, token);\n this.stripHash(); // remove the token from the visible URL\n }\n\n if (this.isExpired()) {\n // Stale token — treat as logged out so the user is prompted for a fresh link.\n sessionStorage.removeItem(TOKEN_STORAGE_KEY);\n this.token = null;\n this.claims = null;\n this.updateAuthState(false);\n return;\n }\n\n this.updateUserInfo(this.toUserInfo(this.claims));\n this.updateAuthState(true);\n }\n\n protected async loginInternal(): Promise<void> {\n // No interactive login path — access is only via a magic-link redemption URL.\n this.updateAuthState(false);\n }\n\n protected async logoutInternal(): Promise<void> {\n sessionStorage.removeItem(TOKEN_STORAGE_KEY);\n this.token = null;\n this.claims = null;\n this.updateUserInfo(null);\n this.updateAuthState(false);\n }\n\n async handleCallback(): Promise<void> {\n // No OAuth callback for magic links.\n }\n\n protected async extractIdTokenInternal(): Promise<string | null> {\n return this.token ?? sessionStorage.getItem(TOKEN_STORAGE_KEY);\n }\n\n protected async extractTokenInfoInternal(): Promise<StandardAuthToken | null> {\n const idToken = await this.extractIdTokenInternal();\n if (!idToken) {\n return null;\n }\n const claims = this.claims ?? this.decode(idToken);\n const exp = claims?.exp;\n return { idToken, expiresAt: exp ? exp * 1000 : 0 };\n }\n\n protected async extractUserInfoInternal(): Promise<StandardUserInfo | null> {\n return this.toUserInfo(this.claims);\n }\n\n protected async refreshTokenInternal(): Promise<TokenRefreshResult> {\n // No refresh tokens for magic-link sessions. Hand back the current token\n // while it's valid; once expired, signal TOKEN_EXPIRED.\n const token = await this.extractTokenInfoInternal();\n if (token && !this.isExpired()) {\n return { success: true, token };\n }\n return {\n success: false,\n error: {\n type: AuthErrorType.TOKEN_EXPIRED,\n message: 'Magic-link session expired',\n userMessage: 'Your access link has expired. Please request a new one.',\n },\n };\n }\n\n protected classifyErrorInternal(error: unknown): StandardAuthError {\n const message = (error as { message?: string })?.message ?? 'Unknown error';\n return { type: AuthErrorType.UNKNOWN_ERROR, message, originalError: error };\n }\n\n protected async getProfilePictureUrlInternal(): Promise<string | null> {\n return null;\n }\n\n protected async handleSessionExpiryInternal(): Promise<void> {\n // No interactive re-auth — the user must redeem a fresh link.\n }\n\n /**\n * Magic-link sessions are locked to the single app the invite scoped to\n * (`mj_app_id`), so the shell hides app-switching and keeps the user there.\n */\n override GetSessionScope(): SessionScope | null {\n const appId = this.claims?.mj_app_id;\n if (this.claims?.mj_magic_link === true && appId) {\n return { restrictedToApplicationId: appId, magicLink: true };\n }\n return null;\n }\n\n override getRequiredConfig(): string[] {\n return [];\n }\n\n override validateConfig(): boolean {\n return true;\n }\n\n // ── helpers ────────────────────────────────────────────────────────────────\n\n /** Reads `token` from the URL fragment (`#token=...`). */\n private readTokenFromHash(): string | null {\n const hash = window.location.hash;\n if (!hash) {\n return null;\n }\n return new URLSearchParams(hash.replace(/^#/, '')).get('token');\n }\n\n /** Removes the token from the address bar without reloading. */\n private stripHash(): void {\n history.replaceState(null, '', window.location.pathname + window.location.search);\n }\n\n /**\n * Decodes a JWT payload (no verification — the server validates via JWKS).\n * UTF-8-safe: external users routinely have non-ASCII names, which a plain\n * `atob` would mangle. Also restores base64url padding `atob` needs.\n */\n private decode(token: string): MagicLinkSessionClaims | null {\n try {\n const segment = token.split('.')[1];\n if (!segment) {\n return null;\n }\n let b64 = segment.replace(/-/g, '+').replace(/_/g, '/');\n b64 += '='.repeat((4 - (b64.length % 4)) % 4);\n const json = decodeURIComponent(\n atob(b64)\n .split('')\n .map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join(''),\n );\n return JSON.parse(json) as MagicLinkSessionClaims;\n } catch (e) {\n // A malformed token must not be silently indistinguishable from \"no token\".\n console.error('[MagicLink] Failed to decode session token:', e);\n return null;\n }\n }\n\n private isExpired(): boolean {\n const exp = this.claims?.exp;\n return !!exp && Date.now() >= exp * 1000;\n }\n\n private toUserInfo(claims: MagicLinkSessionClaims | null): StandardUserInfo | null {\n if (!claims) {\n return null;\n }\n const email = claims.email || '';\n return {\n id: claims.sub || email,\n email,\n name: claims.name || email,\n givenName: claims.given_name,\n familyName: claims.family_name,\n preferredUsername: email,\n emailVerified: true,\n };\n }\n}\n"]}
@@ -8,4 +8,5 @@ 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-magic-link-provider.service';
11
12
  //# 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"}
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"}
@@ -15,4 +15,5 @@ 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-magic-link-provider.service';
18
19
  //# 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","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';"]}
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';"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-auth-services",
3
- "version": "5.38.0",
3
+ "version": "5.40.0",
4
4
  "description": "MemberJunction Explorer: Authentication Services",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -31,8 +31,8 @@
31
31
  "aws-amplify": "^6.0.0"
32
32
  },
33
33
  "dependencies": {
34
- "@memberjunction/core": "5.38.0",
35
- "@memberjunction/global": "5.38.0",
34
+ "@memberjunction/core": "5.40.0",
35
+ "@memberjunction/global": "5.40.0",
36
36
  "rxjs": "^7.8.2",
37
37
  "tslib": "^2.8.1"
38
38
  },