@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.
- package/LICENSE +180 -4
- package/README.md +58 -1
- package/dist/lib/auth-provider-catalog.d.ts +103 -0
- package/dist/lib/auth-provider-catalog.d.ts.map +1 -0
- package/dist/lib/auth-provider-catalog.js +186 -0
- package/dist/lib/auth-provider-catalog.js.map +1 -0
- package/dist/lib/auth-services.module.d.ts +33 -3
- package/dist/lib/auth-services.module.d.ts.map +1 -1
- package/dist/lib/auth-services.module.js +64 -18
- package/dist/lib/auth-services.module.js.map +1 -1
- package/dist/lib/catalog-environment.d.ts +50 -0
- package/dist/lib/catalog-environment.d.ts.map +1 -0
- package/dist/lib/catalog-environment.js +87 -0
- package/dist/lib/catalog-environment.js.map +1 -0
- package/dist/lib/login-picker.component.d.ts +72 -0
- package/dist/lib/login-picker.component.d.ts.map +1 -0
- package/dist/lib/login-picker.component.js +284 -0
- package/dist/lib/login-picker.component.js.map +1 -0
- package/dist/lib/providers/mjexplorer-cognito-provider.service.d.ts.map +1 -1
- package/dist/lib/providers/mjexplorer-cognito-provider.service.js +4 -1
- package/dist/lib/providers/mjexplorer-cognito-provider.service.js.map +1 -1
- package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts +16 -0
- package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts.map +1 -1
- package/dist/lib/providers/mjexplorer-msal-provider.service.js +28 -0
- package/dist/lib/providers/mjexplorer-msal-provider.service.js.map +1 -1
- package/dist/public-api.d.ts +3 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +4 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +7 -6
package/LICENSE
CHANGED
|
@@ -1,7 +1,183 @@
|
|
|
1
|
-
|
|
1
|
+
Business Source License 1.1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
License text copyright (c) 2024 MariaDB plc, All Rights Reserved.
|
|
4
|
+
"Business Source License" is a trademark of MariaDB plc.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
-----------------------------------------------------------------------------
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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"}
|