@memberjunction/ng-auth-services 6.1.0-edge.1 → 6.1.0-edge.3

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.
Files changed (30) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +58 -1
  3. package/dist/lib/auth-provider-catalog.d.ts +103 -0
  4. package/dist/lib/auth-provider-catalog.d.ts.map +1 -0
  5. package/dist/lib/auth-provider-catalog.js +186 -0
  6. package/dist/lib/auth-provider-catalog.js.map +1 -0
  7. package/dist/lib/auth-services.module.d.ts +33 -3
  8. package/dist/lib/auth-services.module.d.ts.map +1 -1
  9. package/dist/lib/auth-services.module.js +64 -18
  10. package/dist/lib/auth-services.module.js.map +1 -1
  11. package/dist/lib/catalog-environment.d.ts +50 -0
  12. package/dist/lib/catalog-environment.d.ts.map +1 -0
  13. package/dist/lib/catalog-environment.js +87 -0
  14. package/dist/lib/catalog-environment.js.map +1 -0
  15. package/dist/lib/login-picker.component.d.ts +72 -0
  16. package/dist/lib/login-picker.component.d.ts.map +1 -0
  17. package/dist/lib/login-picker.component.js +284 -0
  18. package/dist/lib/login-picker.component.js.map +1 -0
  19. package/dist/lib/providers/mjexplorer-cognito-provider.service.d.ts.map +1 -1
  20. package/dist/lib/providers/mjexplorer-cognito-provider.service.js +4 -1
  21. package/dist/lib/providers/mjexplorer-cognito-provider.service.js.map +1 -1
  22. package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts +16 -0
  23. package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts.map +1 -1
  24. package/dist/lib/providers/mjexplorer-msal-provider.service.js +28 -0
  25. package/dist/lib/providers/mjexplorer-msal-provider.service.js.map +1 -1
  26. package/dist/public-api.d.ts +3 -0
  27. package/dist/public-api.d.ts.map +1 -1
  28. package/dist/public-api.js +4 -0
  29. package/dist/public-api.js.map +1 -1
  30. package/package.json +7 -6
package/LICENSE CHANGED
@@ -1,7 +1,183 @@
1
- ISC License
1
+ Business Source License 1.1
2
2
 
3
- Copyright (c) 2023 MemberJunction
3
+ License text copyright (c) 2024 MariaDB plc, All Rights Reserved.
4
+ "Business Source License" is a trademark of MariaDB plc.
4
5
 
5
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+ -----------------------------------------------------------------------------
6
7
 
7
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
8
+ Parameters
9
+
10
+ Licensor: Blue Cypress, Inc.
11
+
12
+ Licensed Work: MemberJunction.
13
+ The Licensed Work is (c) 2023-2026 Blue Cypress, Inc.
14
+
15
+ Additional Use Grant: Subject to the terms of this License, Licensor grants
16
+ you the following additional rights to make Production
17
+ Use of the Licensed Work.
18
+
19
+ 1. Internal Use
20
+
21
+ You may make production use of the Licensed Work for
22
+ your own internal business or organizational operations.
23
+
24
+ 2. Nonprofit Use
25
+
26
+ If you are a Nonprofit, you may make production use of
27
+ the Licensed Work for the operations and activities of
28
+ your Organizational Family.
29
+
30
+ 3. MemberJunction Certified Program Use
31
+
32
+ If you are authorized by Licensor under the
33
+ MemberJunction Certified Program to provide professional
34
+ services using the Licensed Work, you may make
35
+ production use of the Licensed Work in providing such
36
+ professional services to a client, provided that:
37
+
38
+ (a) the Licensed Work is deployed in, and the applicable
39
+ production use occurs within, an environment owned,
40
+ leased, licensed, subscribed to, or otherwise controlled
41
+ by that client; and
42
+
43
+ (b) the production use is for that client's own internal
44
+ business or organizational operations or is otherwise
45
+ independently permitted to that client under this
46
+ Additional Use Grant.
47
+
48
+ 4. Definitions Applicable to the Additional Use Grant
49
+
50
+ "Affiliate" means, with respect to a specified Person,
51
+ any other Person that directly or indirectly Controls,
52
+ is Controlled by, or is under common Control with such
53
+ specified Person.
54
+
55
+ "Control" (including the terms "Controls," "Controlled
56
+ by," and "under common Control with") means the direct
57
+ or indirect possession of the power to direct or cause
58
+ the direction of the management and policies of a
59
+ Person, whether through ownership of voting interests,
60
+ by contract, or otherwise.
61
+
62
+ "Organizational Family" means, with respect to a Person,
63
+ (a) such Person and its Affiliates, and (b) any
64
+ nonprofit organization, governmental entity, chapter,
65
+ division, local affiliate, regional affiliate, state
66
+ affiliate, national affiliate, or other entity that is
67
+ formally affiliated with such Person through governing
68
+ documents, a charter, bylaws, a membership agreement, or
69
+ another written organizational instrument, and is
70
+ recognized under such documents as part of the same
71
+ organizational structure.
72
+
73
+ "Nonprofit" means a Person recognized by the Internal
74
+ Revenue Service as exempt from federal income taxation
75
+ under Section 501(c)(3), 501(c)(4), 501(c)(5), or
76
+ 501(c)(6) of the Internal Revenue Code, or a foreign
77
+ organization recognized under substantially equivalent
78
+ laws.
79
+
80
+ A Person claiming eligibility as a Nonprofit shall, upon
81
+ Licensor's reasonable request, provide documentation
82
+ reasonably sufficient to demonstrate that it qualifies
83
+ as a Nonprofit. If such Person materially misrepresents,
84
+ or is unable to demonstrate, its qualification as a
85
+ Nonprofit, the rights granted to such Person under
86
+ Section 2 of this Additional Use Grant shall terminate.
87
+
88
+ "MemberJunction Certified Program" means Licensor's
89
+ then-current program for certifying and authorizing a
90
+ Person to provide professional services using the
91
+ Licensed Work.
92
+
93
+ "Person" means any individual, corporation, limited
94
+ liability company, partnership, association, nonprofit
95
+ organization, governmental entity, or other legal or
96
+ organizational entity.
97
+
98
+ Change Date: Four (4) years from the date the Licensed Work is first
99
+ made available.
100
+
101
+ Change License: MIT License.
102
+
103
+ For information about alternative licensing arrangements for the Licensed
104
+ Work, please contact Blue Cypress, Inc.
105
+
106
+ -----------------------------------------------------------------------------
107
+
108
+ Terms
109
+
110
+ The Licensor hereby grants you the right to copy, modify, create derivative
111
+ works, redistribute, and make non-production use of the Licensed Work. The
112
+ Licensor may make an Additional Use Grant, above, permitting limited
113
+ production use.
114
+
115
+ Effective on the Change Date, or the fourth anniversary of the first publicly
116
+ available distribution of a specific version of the Licensed Work under this
117
+ License, whichever comes first, the Licensor hereby grants you rights under
118
+ the terms of the Change License, and the rights granted in the paragraph
119
+ above terminate.
120
+
121
+ If your use of the Licensed Work does not comply with the requirements
122
+ currently in effect as described in this License, you must purchase a
123
+ commercial license from the Licensor, its affiliated entities, or authorized
124
+ resellers, or you must refrain from using the Licensed Work.
125
+
126
+ All copies of the original and modified Licensed Work, and derivative works
127
+ of the Licensed Work, are subject to this License. This License applies
128
+ separately for each version of the Licensed Work and the Change Date may vary
129
+ for each version of the Licensed Work released by Licensor.
130
+
131
+ You must conspicuously display this License on each original or modified copy
132
+ of the Licensed Work. If you receive the Licensed Work in original or
133
+ modified form from a third party, the terms and conditions set forth in this
134
+ License apply to your use of that work.
135
+
136
+ Any use of the Licensed Work in violation of this License will automatically
137
+ terminate your rights under this License for the current and all other
138
+ versions of the Licensed Work.
139
+
140
+ This License does not grant you any right in any trademark or logo of
141
+ Licensor or its affiliates (provided that you may use a trademark or logo of
142
+ Licensor as expressly required by this License).
143
+
144
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
145
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
146
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
147
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
148
+ TITLE.
149
+
150
+ MariaDB hereby grants you permission to use this License's text to license
151
+ your works, and to refer to it using the trademark "Business Source License",
152
+ as long as you comply with the Covenants of Licensor below.
153
+
154
+ -----------------------------------------------------------------------------
155
+
156
+ Covenants of Licensor
157
+
158
+ In consideration of the right to use this License's text and the "Business
159
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
160
+ other recipients of the licensed work to be provided by Licensor:
161
+
162
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
163
+ or a license that is compatible with GPL Version 2.0 or a later version,
164
+ where "compatible" means that software provided under the Change License
165
+ can be included in a program with software provided under GPL Version 2.0
166
+ or a later version. Licensor may specify additional Change Licenses without
167
+ limitation.
168
+
169
+ 2. To either: (a) specify an additional grant of rights to use that does not
170
+ impose any additional restriction on the right granted in this License, as
171
+ the Additional Use Grant; or (b) insert the text "None".
172
+
173
+ 3. To specify a Change Date.
174
+
175
+ 4. Not to modify this License in any other way.
176
+
177
+ -----------------------------------------------------------------------------
178
+
179
+ Notice
180
+
181
+ The Business Source License (this document, or the "License") is not an Open
182
+ Source license. However, the Licensed Work will eventually be made available
183
+ under an Open Source License, as stated in this License.
package/README.md CHANGED
@@ -38,6 +38,57 @@ graph TD
38
38
  - **Observable State**: Reactive authentication state, user info, and email streams
39
39
  - **Configuration Validation**: Each provider declares and validates its required configuration
40
40
  - **Angular 21+ Compatible**: Built for modern Angular applications
41
+ - **Metadata-driven provider catalog**: the server publishes its configured providers pre-auth, and the app resolves which one to bootstrap from that catalog (falling back to `AUTH_TYPE`)
42
+ - **Multi-IdP login picker**: reusable, app-agnostic `<mj-login-picker>` rendered when 2+ providers are available
43
+
44
+ ## Metadata-driven providers and the login picker
45
+
46
+ The server can define providers as data (`MJ: Authentication Providers`) and publishes the
47
+ non-secret subset at `GET /auth/providers`. The browser reads that **before login** and decides
48
+ which provider to wire into DI.
49
+
50
+ **Bootstrap.** `AuthServicesModule.forRoot()` runs at module-definition time, so the catalog must
51
+ be fetched before the root module is imported:
52
+
53
+ ```typescript
54
+ // main.ts
55
+ await AuthProviderCatalog.Preload(environment.GRAPHQL_URI);
56
+ const { AppModule } = await import('./app/app.module'); // dynamic import is REQUIRED — see below
57
+ platformBrowserDynamic().bootstrapModule(AppModule);
58
+ ```
59
+
60
+ The dynamic import is not stylistic: a static import is hoisted and would evaluate `AppModule`
61
+ (and `forRoot`) before the `await`, making the preload pointless. `forRoot()` reads the preload
62
+ itself — do **not** pass `AuthProviderCatalog.GetPreloaded()` as an argument, because Angular's
63
+ compiler rejects a function call in an `imports` array (`Value could not be determined statically`).
64
+
65
+ `Preload` never rejects. A 404 (older server), a network failure or a malformed body all yield an
66
+ empty catalog, and resolution falls back to `environment.AUTH_TYPE` — behaviour identical to before
67
+ the catalog existed.
68
+
69
+ **Rendering the picker.** Inject `MJ_AUTH_PROVIDER_RESOLUTION` and embed the shared component in
70
+ your own login surface:
71
+
72
+ ```html
73
+ @if (resolution.showPicker) {
74
+ <mj-login-picker [Providers]="resolution.choices" [Busy]="SigningIn"
75
+ (ProviderSelected)="OnProviderSelected($event)"></mj-login-picker>
76
+ }
77
+ ```
78
+
79
+ `showPicker` is true only with 2+ providers — one option is not a choice, so single-provider
80
+ deployments render exactly as they always have.
81
+
82
+ **Switching providers reloads the page**, by necessity: each browser SDK contributes Angular
83
+ providers (interceptors, guards, config tokens) at module-definition time, so a live injector
84
+ cannot be re-composed. `AuthProviderCatalog.Select()` reports whether a reload is needed — choosing
85
+ the already-active provider logs in immediately; choosing another persists the choice, reloads, and
86
+ resumes login automatically.
87
+
88
+ **Catalog → driver config.** A row's values are projected onto the prefixed environment keys the
89
+ drivers already read (`AUTH0_DOMAIN`, `WORKOS_CLIENTID`, …), which covers Auth0/Okta/Cognito/WorkOS
90
+ with no per-driver code. A driver that reads different keys (MSAL uses unprefixed `CLIENT_ID` /
91
+ `CLIENT_AUTHORITY`) supplies a static `EnvironmentFromCatalog(info)` and maps the row itself.
41
92
 
42
93
  ## Installation
43
94
 
@@ -176,6 +227,12 @@ try {
176
227
  | `AuthErrorType` | Enum | Semantic error categories |
177
228
  | `TokenRefreshResult` | Interface | Token refresh operation result |
178
229
  | `AuthState` | Interface | Complete authentication state snapshot |
230
+ | `AuthProviderCatalog` | Class (static) | Pre-auth catalog fetch, provider resolution, and selection persistence |
231
+ | `AuthProviderResolution` | Interface | Which provider this page load bootstrapped, the choices, and whether to show the picker |
232
+ | `MJ_AUTH_PROVIDER_RESOLUTION` | InjectionToken | The resolution, for login surfaces |
233
+ | `MJLoginPickerComponent` | Component | Reusable `<mj-login-picker>` multi-IdP picker |
234
+ | `CatalogEnvironmentMapper` | Interface | Optional `EnvironmentFromCatalog` static for drivers with non-conventional keys |
235
+ | `mergeCatalogEnvironment` | Function | Projects a catalog row onto a driver's environment keys |
179
236
 
180
237
  ## Build
181
238
 
@@ -185,4 +242,4 @@ cd packages/Angular/Explorer/auth-services && npm run build
185
242
 
186
243
  ## License
187
244
 
188
- ISC
245
+ Business Source License 1.1 — see [LICENSE](../../../../LICENSE) for details.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @fileoverview Pre-authentication provider catalog.
3
+ *
4
+ * The browser needs to know which identity providers exist BEFORE anyone is signed in,
5
+ * so this module deliberately contains no Angular DI and no MJ data access — it runs
6
+ * before `bootstrapModule()`, using only `fetch` and web storage.
7
+ *
8
+ * @module @memberjunction/ng-auth-services
9
+ */
10
+ import { type PublicAuthProviderInfo } from '@memberjunction/core';
11
+ /**
12
+ * How the app should bootstrap authentication, derived from the catalog plus any prior
13
+ * user selection.
14
+ */
15
+ export interface AuthProviderResolution {
16
+ /**
17
+ * The provider to wire into Angular DI for this page load. Null when the catalog is
18
+ * empty, in which case the caller falls back to `environment.AUTH_TYPE`.
19
+ */
20
+ active: PublicAuthProviderInfo | null;
21
+ /**
22
+ * Every provider the user may choose from. Empty or single-entry lists mean the picker
23
+ * should not render — one provider is not a choice.
24
+ */
25
+ choices: PublicAuthProviderInfo[];
26
+ /** True when the picker should be shown (2+ providers available). */
27
+ showPicker: boolean;
28
+ /**
29
+ * True when the previous page load persisted a selection and asked for the login flow to
30
+ * start immediately. The surface should consume this once and call `login()`.
31
+ */
32
+ autoLogin: boolean;
33
+ }
34
+ /**
35
+ * Fetches, caches, and resolves the public authentication-provider catalog.
36
+ *
37
+ * All members are static: this runs before the Angular injector exists.
38
+ */
39
+ export declare class AuthProviderCatalog {
40
+ /**
41
+ * Catalog fetched during app startup, before the Angular module graph is evaluated.
42
+ *
43
+ * A holder is needed because `AuthServicesModule.forRoot(...)` runs at module-DEFINITION time
44
+ * — while the `@NgModule` decorator's `imports` array is being built — which is far too early
45
+ * to await anything. The app fetches the catalog first (see {@link Preload}) and the module
46
+ * reads the result synchronously.
47
+ */
48
+ private static _preloaded;
49
+ /**
50
+ * Fetches the catalog and stores it for {@link GetPreloaded}. Call this from the app's
51
+ * bootstrap entry point BEFORE the root module is imported.
52
+ */
53
+ static Preload(graphqlUri: string, timeoutMs?: number): Promise<PublicAuthProviderInfo[]>;
54
+ /** The catalog stored by {@link Preload}; empty when preloading never ran or found nothing. */
55
+ static GetPreloaded(): PublicAuthProviderInfo[];
56
+ /**
57
+ * Builds the catalog URL from the app's GraphQL endpoint. The catalog is served by the
58
+ * same MJServer instance, mounted ahead of the auth middleware.
59
+ */
60
+ static BuildCatalogUrl(graphqlUri: string): string;
61
+ /**
62
+ * Fetches the public provider catalog.
63
+ *
64
+ * Never throws and never rejects: a deployment whose server predates this endpoint, or
65
+ * whose network is briefly unavailable, must still reach its login screen through the
66
+ * compiled `AUTH_TYPE` path. Failure is reported as an empty list.
67
+ */
68
+ static Fetch(graphqlUri: string, timeoutMs?: number): Promise<PublicAuthProviderInfo[]>;
69
+ /**
70
+ * Decides which provider this page load should bootstrap with.
71
+ *
72
+ * Precedence: the user's persisted choice, then the row flagged `IsDefault`, then the
73
+ * first by sequence. A persisted choice that no longer exists in the catalog (the admin
74
+ * disabled it) is discarded rather than honoured, so a removed provider cannot strand a
75
+ * returning user on a login screen that can never succeed.
76
+ */
77
+ static Resolve(catalog: PublicAuthProviderInfo[]): AuthProviderResolution;
78
+ /**
79
+ * Records the user's picker choice and reports whether the page must reload to apply it.
80
+ *
81
+ * A reload is required whenever the chosen provider differs from the one already wired
82
+ * into DI: each browser SDK contributes Angular providers (interceptors, guards, config
83
+ * tokens) at module-definition time, so switching providers cannot be done in a live
84
+ * injector. Choosing the provider that is already active needs no reload — the caller
85
+ * simply calls `login()`.
86
+ */
87
+ static Select(provider: PublicAuthProviderInfo, activeProviderName: string | null): {
88
+ requiresReload: boolean;
89
+ };
90
+ /** The provider name persisted by a previous picker choice, if any. */
91
+ static GetSelectedProviderName(): string | null;
92
+ /** Forgets the persisted choice so the next load resolves the default again. */
93
+ static ClearSelection(): void;
94
+ /**
95
+ * Reads and clears the "start login immediately" flag. Clearing on read is what keeps a
96
+ * failed or cancelled login from re-triggering the redirect on every subsequent load.
97
+ */
98
+ private static consumePendingLogin;
99
+ private static readStorage;
100
+ private static writeStorage;
101
+ private static removeStorage;
102
+ }
103
+ //# sourceMappingURL=auth-provider-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider-catalog.d.ts","sourceRoot":"","sources":["../../src/lib/auth-provider-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAuD,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAsBxH;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAEtC;;;OAGG;IACH,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAElC,qEAAqE;IACrE,UAAU,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU,CAAgC;IAEzD;;;OAGG;WACiB,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAKtG,+FAA+F;WACjF,YAAY,IAAI,sBAAsB,EAAE;IAItD;;;OAGG;WACW,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIzD;;;;;;OAMG;WACiB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,MAAiC,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IA0B9H;;;;;;;OAOG;WACW,OAAO,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,sBAAsB;IAwBhF;;;;;;;;OAQG;WACW,MAAM,CAAC,QAAQ,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAAG;QAAE,cAAc,EAAE,OAAO,CAAA;KAAE;IAWtH,uEAAuE;WACzD,uBAAuB,IAAI,MAAM,GAAG,IAAI;IAItD,gFAAgF;WAClE,cAAc,IAAI,IAAI;IAKpC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAYlC,OAAO,CAAC,MAAM,CAAC,WAAW;IAQ1B,OAAO,CAAC,MAAM,CAAC,YAAY;IAQ3B,OAAO,CAAC,MAAM,CAAC,aAAa;CAO7B"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @fileoverview Pre-authentication provider catalog.
3
+ *
4
+ * The browser needs to know which identity providers exist BEFORE anyone is signed in,
5
+ * so this module deliberately contains no Angular DI and no MJ data access — it runs
6
+ * before `bootstrapModule()`, using only `fetch` and web storage.
7
+ *
8
+ * @module @memberjunction/ng-auth-services
9
+ */
10
+ import { LogError, LogStatus } from '@memberjunction/core';
11
+ /**
12
+ * Storage key holding the provider the user last chose from the login picker.
13
+ *
14
+ * **Why web storage and not `UserInfoEngine`** (which is the rule for user preferences):
15
+ * this value is read and written while the user is ANONYMOUS. There is no `UserInfo`, no
16
+ * provider, and no API session to persist a preference into — resolving the provider is
17
+ * precisely what has to happen before any of those exist. This is the same documented
18
+ * exception the auth SDKs already rely on for their own session storage.
19
+ */
20
+ const SELECTED_PROVIDER_KEY = 'mj_auth_selected_provider';
21
+ /**
22
+ * Storage key set when a picker choice requires a page reload, so the reloaded app knows
23
+ * to go straight into the provider's login flow instead of showing the picker again.
24
+ */
25
+ const PENDING_LOGIN_KEY = 'mj_auth_pending_login';
26
+ /** How long to wait for the catalog before falling back to the compiled configuration. */
27
+ const DEFAULT_FETCH_TIMEOUT_MS = 5000;
28
+ /**
29
+ * Fetches, caches, and resolves the public authentication-provider catalog.
30
+ *
31
+ * All members are static: this runs before the Angular injector exists.
32
+ */
33
+ export class AuthProviderCatalog {
34
+ /**
35
+ * Catalog fetched during app startup, before the Angular module graph is evaluated.
36
+ *
37
+ * A holder is needed because `AuthServicesModule.forRoot(...)` runs at module-DEFINITION time
38
+ * — while the `@NgModule` decorator's `imports` array is being built — which is far too early
39
+ * to await anything. The app fetches the catalog first (see {@link Preload}) and the module
40
+ * reads the result synchronously.
41
+ */
42
+ static _preloaded = [];
43
+ /**
44
+ * Fetches the catalog and stores it for {@link GetPreloaded}. Call this from the app's
45
+ * bootstrap entry point BEFORE the root module is imported.
46
+ */
47
+ static async Preload(graphqlUri, timeoutMs) {
48
+ this._preloaded = await this.Fetch(graphqlUri, timeoutMs);
49
+ return this._preloaded;
50
+ }
51
+ /** The catalog stored by {@link Preload}; empty when preloading never ran or found nothing. */
52
+ static GetPreloaded() {
53
+ return this._preloaded;
54
+ }
55
+ /**
56
+ * Builds the catalog URL from the app's GraphQL endpoint. The catalog is served by the
57
+ * same MJServer instance, mounted ahead of the auth middleware.
58
+ */
59
+ static BuildCatalogUrl(graphqlUri) {
60
+ return new URL('/auth/providers', graphqlUri).toString();
61
+ }
62
+ /**
63
+ * Fetches the public provider catalog.
64
+ *
65
+ * Never throws and never rejects: a deployment whose server predates this endpoint, or
66
+ * whose network is briefly unavailable, must still reach its login screen through the
67
+ * compiled `AUTH_TYPE` path. Failure is reported as an empty list.
68
+ */
69
+ static async Fetch(graphqlUri, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
70
+ const url = this.BuildCatalogUrl(graphqlUri);
71
+ const controller = new AbortController();
72
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
73
+ try {
74
+ const response = await fetch(url, { method: 'GET', signal: controller.signal, credentials: 'omit' });
75
+ if (!response.ok) {
76
+ // A 404 is the expected, benign answer from a server older than this feature.
77
+ if (response.status !== 404) {
78
+ LogError(`[Auth] Provider catalog request to ${url} failed with HTTP ${response.status}; falling back to configured AUTH_TYPE.`);
79
+ }
80
+ return [];
81
+ }
82
+ const body = (await response.json());
83
+ return Array.isArray(body?.providers) ? body.providers : [];
84
+ }
85
+ catch (error) {
86
+ const message = error instanceof Error ? error.message : String(error);
87
+ LogError(`[Auth] Could not load the provider catalog from ${url} (${message}); falling back to configured AUTH_TYPE.`);
88
+ return [];
89
+ }
90
+ finally {
91
+ clearTimeout(timer);
92
+ }
93
+ }
94
+ /**
95
+ * Decides which provider this page load should bootstrap with.
96
+ *
97
+ * Precedence: the user's persisted choice, then the row flagged `IsDefault`, then the
98
+ * first by sequence. A persisted choice that no longer exists in the catalog (the admin
99
+ * disabled it) is discarded rather than honoured, so a removed provider cannot strand a
100
+ * returning user on a login screen that can never succeed.
101
+ */
102
+ static Resolve(catalog) {
103
+ const choices = [...catalog].sort((a, b) => a.sequence - b.sequence || a.displayName.localeCompare(b.displayName));
104
+ if (choices.length === 0) {
105
+ return { active: null, choices: [], showPicker: false, autoLogin: false };
106
+ }
107
+ const selectedName = this.GetSelectedProviderName();
108
+ const selected = selectedName ? choices.find((p) => p.name === selectedName) : undefined;
109
+ if (selectedName && !selected) {
110
+ LogStatus(`[Auth] Previously selected provider '${selectedName}' is no longer available; falling back to the default.`);
111
+ this.ClearSelection();
112
+ }
113
+ const active = selected ?? choices.find((p) => p.isDefault) ?? choices[0];
114
+ return {
115
+ active,
116
+ choices,
117
+ showPicker: choices.length > 1,
118
+ autoLogin: Boolean(selected) && this.consumePendingLogin()
119
+ };
120
+ }
121
+ /**
122
+ * Records the user's picker choice and reports whether the page must reload to apply it.
123
+ *
124
+ * A reload is required whenever the chosen provider differs from the one already wired
125
+ * into DI: each browser SDK contributes Angular providers (interceptors, guards, config
126
+ * tokens) at module-definition time, so switching providers cannot be done in a live
127
+ * injector. Choosing the provider that is already active needs no reload — the caller
128
+ * simply calls `login()`.
129
+ */
130
+ static Select(provider, activeProviderName) {
131
+ this.writeStorage(SELECTED_PROVIDER_KEY, provider.name);
132
+ if (activeProviderName && provider.name === activeProviderName) {
133
+ return { requiresReload: false };
134
+ }
135
+ this.writeStorage(PENDING_LOGIN_KEY, '1');
136
+ return { requiresReload: true };
137
+ }
138
+ /** The provider name persisted by a previous picker choice, if any. */
139
+ static GetSelectedProviderName() {
140
+ return this.readStorage(SELECTED_PROVIDER_KEY);
141
+ }
142
+ /** Forgets the persisted choice so the next load resolves the default again. */
143
+ static ClearSelection() {
144
+ this.removeStorage(SELECTED_PROVIDER_KEY);
145
+ this.removeStorage(PENDING_LOGIN_KEY);
146
+ }
147
+ /**
148
+ * Reads and clears the "start login immediately" flag. Clearing on read is what keeps a
149
+ * failed or cancelled login from re-triggering the redirect on every subsequent load.
150
+ */
151
+ static consumePendingLogin() {
152
+ const pending = this.readStorage(PENDING_LOGIN_KEY) === '1';
153
+ if (pending) {
154
+ this.removeStorage(PENDING_LOGIN_KEY);
155
+ }
156
+ return pending;
157
+ }
158
+ // ── Storage access ────────────────────────────────────────────────────────
159
+ // Wrapped because localStorage throws on access in some privacy modes and in
160
+ // sandboxed iframes. Auth must degrade to "ask every time", never crash.
161
+ static readStorage(key) {
162
+ try {
163
+ return window.localStorage.getItem(key);
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ static writeStorage(key, value) {
170
+ try {
171
+ window.localStorage.setItem(key, value);
172
+ }
173
+ catch {
174
+ /* Non-fatal: the choice simply won't survive this page load. */
175
+ }
176
+ }
177
+ static removeStorage(key) {
178
+ try {
179
+ window.localStorage.removeItem(key);
180
+ }
181
+ catch {
182
+ /* Non-fatal. */
183
+ }
184
+ }
185
+ }
186
+ //# sourceMappingURL=auth-provider-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider-catalog.js","sourceRoot":"","sources":["../../src/lib/auth-provider-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pre-authentication provider catalog.\n *\n * The browser needs to know which identity providers exist BEFORE anyone is signed in,\n * so this module deliberately contains no Angular DI and no MJ data access — it runs\n * before `bootstrapModule()`, using only `fetch` and web storage.\n *\n * @module @memberjunction/ng-auth-services\n */\n\nimport { LogError, LogStatus, type PublicAuthProviderCatalog, type PublicAuthProviderInfo } from '@memberjunction/core';\n\n/**\n * Storage key holding the provider the user last chose from the login picker.\n *\n * **Why web storage and not `UserInfoEngine`** (which is the rule for user preferences):\n * this value is read and written while the user is ANONYMOUS. There is no `UserInfo`, no\n * provider, and no API session to persist a preference into — resolving the provider is\n * precisely what has to happen before any of those exist. This is the same documented\n * exception the auth SDKs already rely on for their own session storage.\n */\nconst SELECTED_PROVIDER_KEY = 'mj_auth_selected_provider';\n\n/**\n * Storage key set when a picker choice requires a page reload, so the reloaded app knows\n * to go straight into the provider's login flow instead of showing the picker again.\n */\nconst PENDING_LOGIN_KEY = 'mj_auth_pending_login';\n\n/** How long to wait for the catalog before falling back to the compiled configuration. */\nconst DEFAULT_FETCH_TIMEOUT_MS = 5000;\n\n/**\n * How the app should bootstrap authentication, derived from the catalog plus any prior\n * user selection.\n */\nexport interface AuthProviderResolution {\n /**\n * The provider to wire into Angular DI for this page load. Null when the catalog is\n * empty, in which case the caller falls back to `environment.AUTH_TYPE`.\n */\n active: PublicAuthProviderInfo | null;\n\n /**\n * Every provider the user may choose from. Empty or single-entry lists mean the picker\n * should not render — one provider is not a choice.\n */\n choices: PublicAuthProviderInfo[];\n\n /** True when the picker should be shown (2+ providers available). */\n showPicker: boolean;\n\n /**\n * True when the previous page load persisted a selection and asked for the login flow to\n * start immediately. The surface should consume this once and call `login()`.\n */\n autoLogin: boolean;\n}\n\n/**\n * Fetches, caches, and resolves the public authentication-provider catalog.\n *\n * All members are static: this runs before the Angular injector exists.\n */\nexport class AuthProviderCatalog {\n /**\n * Catalog fetched during app startup, before the Angular module graph is evaluated.\n *\n * A holder is needed because `AuthServicesModule.forRoot(...)` runs at module-DEFINITION time\n * — while the `@NgModule` decorator's `imports` array is being built — which is far too early\n * to await anything. The app fetches the catalog first (see {@link Preload}) and the module\n * reads the result synchronously.\n */\n private static _preloaded: PublicAuthProviderInfo[] = [];\n\n /**\n * Fetches the catalog and stores it for {@link GetPreloaded}. Call this from the app's\n * bootstrap entry point BEFORE the root module is imported.\n */\n public static async Preload(graphqlUri: string, timeoutMs?: number): Promise<PublicAuthProviderInfo[]> {\n this._preloaded = await this.Fetch(graphqlUri, timeoutMs);\n return this._preloaded;\n }\n\n /** The catalog stored by {@link Preload}; empty when preloading never ran or found nothing. */\n public static GetPreloaded(): PublicAuthProviderInfo[] {\n return this._preloaded;\n }\n\n /**\n * Builds the catalog URL from the app's GraphQL endpoint. The catalog is served by the\n * same MJServer instance, mounted ahead of the auth middleware.\n */\n public static BuildCatalogUrl(graphqlUri: string): string {\n return new URL('/auth/providers', graphqlUri).toString();\n }\n\n /**\n * Fetches the public provider catalog.\n *\n * Never throws and never rejects: a deployment whose server predates this endpoint, or\n * whose network is briefly unavailable, must still reach its login screen through the\n * compiled `AUTH_TYPE` path. Failure is reported as an empty list.\n */\n public static async Fetch(graphqlUri: string, timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS): Promise<PublicAuthProviderInfo[]> {\n const url = this.BuildCatalogUrl(graphqlUri);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, { method: 'GET', signal: controller.signal, credentials: 'omit' });\n if (!response.ok) {\n // A 404 is the expected, benign answer from a server older than this feature.\n if (response.status !== 404) {\n LogError(`[Auth] Provider catalog request to ${url} failed with HTTP ${response.status}; falling back to configured AUTH_TYPE.`);\n }\n return [];\n }\n\n const body = (await response.json()) as PublicAuthProviderCatalog;\n return Array.isArray(body?.providers) ? body.providers : [];\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n LogError(`[Auth] Could not load the provider catalog from ${url} (${message}); falling back to configured AUTH_TYPE.`);\n return [];\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Decides which provider this page load should bootstrap with.\n *\n * Precedence: the user's persisted choice, then the row flagged `IsDefault`, then the\n * first by sequence. A persisted choice that no longer exists in the catalog (the admin\n * disabled it) is discarded rather than honoured, so a removed provider cannot strand a\n * returning user on a login screen that can never succeed.\n */\n public static Resolve(catalog: PublicAuthProviderInfo[]): AuthProviderResolution {\n const choices = [...catalog].sort((a, b) => a.sequence - b.sequence || a.displayName.localeCompare(b.displayName));\n\n if (choices.length === 0) {\n return { active: null, choices: [], showPicker: false, autoLogin: false };\n }\n\n const selectedName = this.GetSelectedProviderName();\n const selected = selectedName ? choices.find((p) => p.name === selectedName) : undefined;\n if (selectedName && !selected) {\n LogStatus(`[Auth] Previously selected provider '${selectedName}' is no longer available; falling back to the default.`);\n this.ClearSelection();\n }\n\n const active = selected ?? choices.find((p) => p.isDefault) ?? choices[0];\n\n return {\n active,\n choices,\n showPicker: choices.length > 1,\n autoLogin: Boolean(selected) && this.consumePendingLogin()\n };\n }\n\n /**\n * Records the user's picker choice and reports whether the page must reload to apply it.\n *\n * A reload is required whenever the chosen provider differs from the one already wired\n * into DI: each browser SDK contributes Angular providers (interceptors, guards, config\n * tokens) at module-definition time, so switching providers cannot be done in a live\n * injector. Choosing the provider that is already active needs no reload — the caller\n * simply calls `login()`.\n */\n public static Select(provider: PublicAuthProviderInfo, activeProviderName: string | null): { requiresReload: boolean } {\n this.writeStorage(SELECTED_PROVIDER_KEY, provider.name);\n\n if (activeProviderName && provider.name === activeProviderName) {\n return { requiresReload: false };\n }\n\n this.writeStorage(PENDING_LOGIN_KEY, '1');\n return { requiresReload: true };\n }\n\n /** The provider name persisted by a previous picker choice, if any. */\n public static GetSelectedProviderName(): string | null {\n return this.readStorage(SELECTED_PROVIDER_KEY);\n }\n\n /** Forgets the persisted choice so the next load resolves the default again. */\n public static ClearSelection(): void {\n this.removeStorage(SELECTED_PROVIDER_KEY);\n this.removeStorage(PENDING_LOGIN_KEY);\n }\n\n /**\n * Reads and clears the \"start login immediately\" flag. Clearing on read is what keeps a\n * failed or cancelled login from re-triggering the redirect on every subsequent load.\n */\n private static consumePendingLogin(): boolean {\n const pending = this.readStorage(PENDING_LOGIN_KEY) === '1';\n if (pending) {\n this.removeStorage(PENDING_LOGIN_KEY);\n }\n return pending;\n }\n\n // ── Storage access ────────────────────────────────────────────────────────\n // Wrapped because localStorage throws on access in some privacy modes and in\n // sandboxed iframes. Auth must degrade to \"ask every time\", never crash.\n\n private static readStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n private static writeStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n /* Non-fatal: the choice simply won't survive this page load. */\n }\n }\n\n private static removeStorage(key: string): void {\n try {\n window.localStorage.removeItem(key);\n } catch {\n /* Non-fatal. */\n }\n }\n}\n"]}
@@ -1,17 +1,47 @@
1
- import { ModuleWithProviders } from '@angular/core';
1
+ import { ModuleWithProviders, InjectionToken } from '@angular/core';
2
+ import type { PublicAuthProviderInfo } from '@memberjunction/core';
3
+ import { type AuthProviderResolution } from './auth-provider-catalog';
2
4
  import { RedirectComponent } from './redirect.component';
3
5
  import * as i0 from "@angular/core";
4
6
  import * as i1 from "./redirect.component";
5
7
  import * as i2 from "@angular/common";
8
+ import * as i3 from "./login-picker.component";
6
9
  export { RedirectComponent };
10
+ /**
11
+ * Injection token carrying how authentication resolved for this page load.
12
+ *
13
+ * A login surface injects this to decide whether to render the multi-provider picker or the
14
+ * single sign-in button, and whether to start the login flow immediately after a picker
15
+ * choice caused a reload.
16
+ */
17
+ export declare const MJ_AUTH_PROVIDER_RESOLUTION: InjectionToken<AuthProviderResolution>;
7
18
  /**
8
19
  * Extensible authentication module that supports N providers
9
20
  * Uses MJGlobal ClassFactory pattern for dynamic provider creation
10
21
  */
11
22
  export declare class AuthServicesModule {
12
- static forRoot(environment: any): ModuleWithProviders<AuthServicesModule>;
23
+ /**
24
+ * Wires the authentication provider into DI.
25
+ *
26
+ * @param environment The app's compiled environment. Typed loosely on purpose: every app
27
+ * declares its own environment shape (`MJEnvironmentConfig` and downstream equivalents
28
+ * are interfaces, which TypeScript will not assign to an indexed record), so narrowing
29
+ * this parameter would break every existing caller. It is narrowed once, below.
30
+ * @param catalog Optional pre-auth provider catalog. Defaults to whatever the app preloaded
31
+ * via {@link AuthProviderCatalog.Preload} in its bootstrap entry point — passing it
32
+ * explicitly is for tests and for apps that source the catalog some other way. Callers
33
+ * must NOT pass `AuthProviderCatalog.GetPreloaded()` themselves: Angular's compiler
34
+ * requires every `imports` entry to be statically analyzable, and a function call in
35
+ * the argument list fails AOT with "Value could not be determined statically".
36
+ *
37
+ * When the catalog is empty — an older server, a failed fetch, or a deployment that
38
+ * never adopted the entity — resolution falls back to `environment.AUTH_TYPE` and
39
+ * behaviour is byte-for-byte what it was before the catalog existed, which is what
40
+ * keeps every existing deployment working untouched.
41
+ */
42
+ static forRoot(environment: any, catalog?: PublicAuthProviderInfo[]): ModuleWithProviders<AuthServicesModule>;
13
43
  static ɵfac: i0.ɵɵFactoryDeclaration<AuthServicesModule, never>;
14
- static ɵmod: i0.ɵɵNgModuleDeclaration<AuthServicesModule, [typeof i1.RedirectComponent], [typeof i2.CommonModule], [typeof i1.RedirectComponent]>;
44
+ static ɵmod: i0.ɵɵNgModuleDeclaration<AuthServicesModule, [typeof i1.RedirectComponent], [typeof i2.CommonModule, typeof i3.MJLoginPickerComponent], [typeof i1.RedirectComponent, typeof i3.MJLoginPickerComponent]>;
15
45
  static ɵinj: i0.ɵɵInjectorDeclaration<AuthServicesModule>;
16
46
  }
17
47
  //# sourceMappingURL=auth-services.module.d.ts.map
@@ -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;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"}
1
+ {"version":3,"file":"auth-services.module.d.ts","sourceRoot":"","sources":["../../src/lib/auth-services.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,mBAAmB,EAAE,cAAc,EAAY,MAAM,eAAe,CAAC;AAMxF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAuB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAK3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;;;;AAGzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,wCAA4E,CAAC;AAKrH;;;GAGG;AACH,qBAKa,kBAAkB;IAC7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,sBAAsB,EAAE,GAAG,mBAAmB,CAAC,kBAAkB,CAAC;yCApBlG,kBAAkB;0CAAlB,kBAAkB;0CAAlB,kBAAkB;CA6F9B"}