ngx-oauth 7.0.1 → 8.0.1

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,15 +1,6 @@
1
- import * as i3 from '@angular/common/http';
2
- import { HttpHeaders, HttpClient, HttpBackend, HttpParams } from '@angular/common/http';
3
- import * as i0 from '@angular/core';
4
- import { InjectionToken, inject, Injectable, Component, ViewEncapsulation, Input, Output, ContentChild, HostListener } from '@angular/core';
5
- import { BehaviorSubject, distinctUntilChanged, switchMap, of, ReplaySubject, from, noop, firstValueFrom, throwError, take } from 'rxjs';
6
- import { shareReplay, map, catchError, filter, switchMap as switchMap$1, tap, concatMap, delay } from 'rxjs/operators';
7
- import * as i2 from '@angular/common';
8
- import { CommonModule } from '@angular/common';
9
- import * as i3$1 from '@angular/forms';
10
- import { FormsModule } from '@angular/forms';
1
+ import { signal, linkedSignal, makeEnvironmentProviders, provideEnvironmentInitializer, InjectionToken, inject, computed, effect, untracked, resource } from '@angular/core';
2
+ import { jwtVerify, createRemoteJWKSet } from 'jose';
11
3
 
12
- const HEADER_APPLICATION = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });
13
4
  var OAuthType;
14
5
  (function (OAuthType) {
15
6
  OAuthType["RESOURCE"] = "password";
@@ -24,125 +15,389 @@ var OAuthStatus;
24
15
  OAuthStatus["DENIED"] = "DENIED";
25
16
  })(OAuthStatus || (OAuthStatus = {}));
26
17
 
27
- const OAUTH_CONFIG = new InjectionToken('OAuthConfig');
28
- class OAuthConfig {
29
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
30
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthConfig, providedIn: 'root', useFactory: () => inject(OAUTH_CONFIG).reduce((p, c) => ({ ...p, ...c }), defaultOAuthConfig) }); }
31
- }
32
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthConfig, decorators: [{
33
- type: Injectable,
34
- args: [{
35
- providedIn: 'root',
36
- useFactory: () => inject(OAUTH_CONFIG).reduce((p, c) => ({ ...p, ...c }), defaultOAuthConfig)
37
- }]
38
- }] });
39
- const provideOAuthConfig = (config = {}) => ({
40
- provide: OAUTH_CONFIG,
41
- useValue: config,
42
- multi: true
43
- });
44
- const provideOAuthConfigFactory = (factory, deps) => ({
45
- provide: OAUTH_CONFIG,
46
- useFactory: factory,
47
- deps: deps,
48
- multi: true
49
- });
50
- const defaultOAuthConfig = {
51
- storage: globalThis.localStorage,
52
- location: globalThis.location,
18
+ const defaults = {
53
19
  storageKey: 'token',
54
- ignorePaths: []
20
+ ignorePaths: [],
21
+ strictJwt: true
55
22
  };
23
+ const oauthConfig = signal(defaults, ...(ngDevMode ? [{ debugName: "oauthConfig" }] : /* istanbul ignore next */ []));
24
+ const config = linkedSignal(() => oauthConfig().config, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
25
+ const provideOAuthConfig = (cfg = {}) => makeEnvironmentProviders([provideEnvironmentInitializer(() => oauthConfig.set({ ...defaults, ...cfg }))]);
56
26
 
57
- class OAuthHttpClient extends HttpClient {
58
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthHttpClient, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
59
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthHttpClient, providedIn: 'root', useFactory: () => new HttpClient(inject(HttpBackend)) }); }
60
- }
61
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthHttpClient, decorators: [{
62
- type: Injectable,
63
- args: [{
64
- providedIn: 'root',
65
- useFactory: () => new HttpClient(inject(HttpBackend)),
66
- }]
67
- }] });
68
-
69
- const isExpiredToken = (token) => token && token.expires && Date.now() > token.expires || false;
70
- class OAuthTokenService {
71
- #token$;
72
- constructor(authConfig, http) {
73
- this.authConfig = authConfig;
74
- this.http = http;
75
- this.#token$ = new BehaviorSubject(this.saved);
76
- this.token$ = this.#token$.pipe(distinctUntilChanged((p, c) => JSON.stringify(p || null) === JSON.stringify(c || null)), shareReplay(1), switchMap(token => !isExpiredToken(token) && of(token) || this.refreshToken(token)));
77
- this.type$ = this.token$.pipe(map(token => token?.type), shareReplay(1));
78
- this.accessToken$ = this.token$.pipe(map(token => token?.access_token), shareReplay(1));
27
+ const storage = () => {
28
+ const s = globalThis.localStorage;
29
+ return typeof s?.getItem === 'function' ? s : undefined;
30
+ };
31
+ const get = (key) => {
32
+ const value = storage()?.getItem(key);
33
+ try {
34
+ return (value && JSON.parse(value)) || undefined;
79
35
  }
80
- get token() {
81
- return this.#token$.value;
36
+ catch {
37
+ return undefined;
82
38
  }
83
- set token(token) {
84
- const expiresIn = Number(token.expires_in) || 0;
85
- const result = {
86
- ...token,
87
- ...expiresIn && { expires: Date.now() + expiresIn * 1000 } || {}
88
- };
89
- this.saved = result;
90
- this.#token$.next(result);
39
+ };
40
+ const set = (key, value) => {
41
+ storage()?.setItem(key, JSON.stringify(value));
42
+ };
43
+ const storageSignal = (keyInput, defaultValue) => {
44
+ const keyFn = typeof keyInput === 'function' ? keyInput : () => keyInput;
45
+ const s = signal(get(keyFn()) ?? defaultValue, ...(ngDevMode ? [{ debugName: "s" }] : /* istanbul ignore next */ []));
46
+ const { set: signalSet, update } = s;
47
+ s.set = value => {
48
+ set(keyFn(), value);
49
+ signalSet(value);
50
+ };
51
+ s.update = fn => {
52
+ update(current => {
53
+ const next = fn(current);
54
+ set(keyFn(), next);
55
+ return next;
56
+ });
57
+ };
58
+ return s;
59
+ };
60
+
61
+ const HEADERS = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' };
62
+ const refresh = async (token, config) => {
63
+ const { tokenPath, clientId, clientSecret, scope } = config || {};
64
+ const { refresh_token, type } = token || {};
65
+ if (!refresh_token || !tokenPath)
66
+ return token;
67
+ const result = await fetch(tokenPath, {
68
+ method: 'POST',
69
+ headers: HEADERS,
70
+ body: new URLSearchParams({
71
+ client_id: clientId,
72
+ ...((clientSecret && { client_secret: clientSecret }) || {}),
73
+ grant_type: 'refresh_token',
74
+ refresh_token,
75
+ ...((scope && { scope }) || {})
76
+ })
77
+ }).then(r => r.json());
78
+ return result ? { ...result, type } : token;
79
+ };
80
+ const revoke = async (token, config) => {
81
+ const { revokePath, clientId, clientSecret } = config || {};
82
+ if (!revokePath)
83
+ return;
84
+ const { access_token, refresh_token } = token || {};
85
+ const base = {
86
+ ...((clientId && { client_id: clientId }) || {}),
87
+ ...((clientSecret && { client_secret: clientSecret }) || {})
88
+ };
89
+ if (access_token) {
90
+ await fetch(revokePath, {
91
+ method: 'POST',
92
+ headers: HEADERS,
93
+ body: new URLSearchParams({ ...base, token: access_token, token_type_hint: 'access_token' })
94
+ });
91
95
  }
92
- get saved() {
93
- const { storageKey, storage } = this.authConfig;
94
- return storageKey && storage && storage[storageKey] && JSON.parse(storage[storageKey]) || {};
96
+ if (refresh_token) {
97
+ await fetch(revokePath, {
98
+ method: 'POST',
99
+ headers: HEADERS,
100
+ body: new URLSearchParams({ ...base, token: refresh_token, token_type_hint: 'refresh_token' })
101
+ });
95
102
  }
96
- set saved(token) {
97
- const { storageKey, storage } = this.authConfig;
98
- if (storage && storageKey) {
99
- if (token) {
100
- storage[storageKey] = JSON.stringify(token);
103
+ };
104
+ const authorize = async (token, config) => {
105
+ const { clientId, clientSecret, tokenPath, scope } = config || {};
106
+ const { code, redirect_uri, code_verifier } = token || {};
107
+ if (!code || !tokenPath)
108
+ return token;
109
+ const result = await fetch(tokenPath, {
110
+ method: 'POST',
111
+ headers: HEADERS,
112
+ body: new URLSearchParams({
113
+ code,
114
+ client_id: clientId,
115
+ ...((clientSecret && { client_secret: clientSecret }) || {}),
116
+ redirect_uri: redirect_uri,
117
+ grant_type: 'authorization_code',
118
+ ...((scope && { scope }) || {}),
119
+ ...((code_verifier && { code_verifier }) || {})
120
+ })
121
+ }).then(r => r.json());
122
+ return result ? { ...result, type: OAuthType.AUTHORIZATION_CODE } : token;
123
+ };
124
+ const clientCredentialLogin = async (config) => {
125
+ const { clientId, clientSecret, tokenPath, scope } = config || {};
126
+ if (!tokenPath)
127
+ return undefined;
128
+ const result = await fetch(tokenPath, {
129
+ method: 'POST',
130
+ headers: HEADERS,
131
+ body: new URLSearchParams({
132
+ client_id: clientId,
133
+ client_secret: clientSecret,
134
+ grant_type: OAuthType.CLIENT_CREDENTIAL,
135
+ ...(scope ? { scope } : {})
136
+ })
137
+ }).then(r => r.json());
138
+ return result ? { ...result, type: OAuthType.CLIENT_CREDENTIAL } : undefined;
139
+ };
140
+ const resourceOwnerLogin = async (parameters, config) => {
141
+ const { clientId, clientSecret, tokenPath, scope } = config || {};
142
+ const { username, password } = parameters || {};
143
+ if (!tokenPath || !clientId)
144
+ return undefined;
145
+ const result = await fetch(tokenPath, {
146
+ method: 'POST',
147
+ headers: HEADERS,
148
+ body: new URLSearchParams({
149
+ client_id: clientId,
150
+ ...((clientSecret && { client_secret: clientSecret }) || {}),
151
+ grant_type: OAuthType.RESOURCE,
152
+ ...((scope && { scope }) || {}),
153
+ username: username,
154
+ password: password
155
+ })
156
+ }).then(r => r.json());
157
+ return result ? { ...result, type: OAuthType.RESOURCE } : undefined;
158
+ };
159
+ const openIdConfiguration = async (config) => {
160
+ const { issuerPath, clientId } = config || {};
161
+ if (!issuerPath)
162
+ return undefined;
163
+ return fetch(`${issuerPath}/.well-known/openid-configuration?client_id=${clientId}`).then(r => r.json());
164
+ };
165
+ const userInfo = async (config, fetchFn = fetch) => {
166
+ const { userPath } = config || {};
167
+ if (!userPath)
168
+ return undefined;
169
+ return fetchFn(userPath).then(r => r.json());
170
+ };
171
+ const introspect = async (token, config) => {
172
+ const { introspectionPath, clientId, clientSecret } = config || {};
173
+ const { access_token } = token || {};
174
+ if (!introspectionPath || !access_token || !clientId)
175
+ return undefined;
176
+ return fetch(introspectionPath, {
177
+ method: 'POST',
178
+ headers: { ...HEADERS, Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}` },
179
+ body: new URLSearchParams({ token: access_token })
180
+ }).then(r => r.json());
181
+ };
182
+ const OAUTH_REFRESH = new InjectionToken('OAUTH_REFRESH', {
183
+ providedIn: 'root',
184
+ factory: () => refresh
185
+ });
186
+ const OAUTH_REVOKE = new InjectionToken('OAUTH_REVOKE', {
187
+ providedIn: 'root',
188
+ factory: () => revoke
189
+ });
190
+ const OAUTH_AUTHORIZE = new InjectionToken('OAUTH_AUTHORIZE', {
191
+ providedIn: 'root',
192
+ factory: () => authorize
193
+ });
194
+ const OAUTH_CLIENT_CREDENTIAL = new InjectionToken('OAUTH_CLIENT_CREDENTIAL', {
195
+ providedIn: 'root',
196
+ factory: () => clientCredentialLogin
197
+ });
198
+ const OAUTH_RESOURCE_OWNER = new InjectionToken('OAUTH_RESOURCE_OWNER', {
199
+ providedIn: 'root',
200
+ factory: () => resourceOwnerLogin
201
+ });
202
+ const OAUTH_OPENID_CONFIG = new InjectionToken('OAUTH_OPENID_CONFIG', {
203
+ providedIn: 'root',
204
+ factory: () => openIdConfiguration
205
+ });
206
+ const OAUTH_USER_INFO = new InjectionToken('OAUTH_USER_INFO', {
207
+ providedIn: 'root',
208
+ factory: () => userInfo
209
+ });
210
+ const OAUTH_INTROSPECT = new InjectionToken('OAUTH_INTROSPECT', {
211
+ providedIn: 'root',
212
+ factory: () => introspect
213
+ });
214
+
215
+ const isExpiredToken = (token) => (token?.expires && Date.now() > token.expires) || false;
216
+ const OAUTH_TOKEN = new InjectionToken('OAUTH_TOKEN', {
217
+ providedIn: 'root',
218
+ factory: () => {
219
+ const refresh = inject(OAUTH_REFRESH);
220
+ const openIdConfiguration = inject(OAUTH_OPENID_CONFIG);
221
+ const storageKey = linkedSignal(() => oauthConfig().storageKey, ...(ngDevMode ? [{ debugName: "storageKey" }] : /* istanbul ignore next */ []));
222
+ const token = storageSignal(storageKey, {});
223
+ const type = computed(() => token().type, ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
224
+ const accessToken = computed(() => {
225
+ const { token_type, access_token } = token() || {};
226
+ return (token_type && access_token && `${token_type} ${access_token}`) || undefined;
227
+ }, ...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
228
+ const status = computed(() => {
229
+ const { error, access_token } = token();
230
+ return ((error && OAuthStatus.DENIED) || (access_token && !isExpiredToken(token()) && OAuthStatus.AUTHORIZED) || OAuthStatus.NOT_AUTHORIZED);
231
+ }, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
232
+ const isAuthorized = computed(() => status() === OAuthStatus.AUTHORIZED, ...(ngDevMode ? [{ debugName: "isAuthorized" }] : /* istanbul ignore next */ []));
233
+ const error = computed(() => token().error, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
234
+ const hasError = computed(() => !!error(), ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
235
+ const errorDescription = computed(() => token().error_description, ...(ngDevMode ? [{ debugName: "errorDescription" }] : /* istanbul ignore next */ []));
236
+ const autoconfigOauth = async () => {
237
+ const c = config();
238
+ if (!(c.tokenPath || c.authorizePath)) {
239
+ const v = await openIdConfiguration(c);
240
+ if (v) {
241
+ config.set({
242
+ ...c,
243
+ ...((v?.authorization_endpoint && { authorizePath: v.authorization_endpoint }) || {}),
244
+ ...((v?.token_endpoint && { tokenPath: v.token_endpoint }) || {}),
245
+ ...((v?.revocation_endpoint && { revokePath: v.revocation_endpoint }) || {}),
246
+ ...((v?.userinfo_endpoint && { userPath: v.userinfo_endpoint }) || {}),
247
+ ...((v?.introspection_endpoint && { introspectionPath: v.introspection_endpoint }) || {}),
248
+ ...((v?.end_session_endpoint && { logoutPath: v.end_session_endpoint }) || {}),
249
+ ...((v?.jwks_uri && { jwksUri: v.jwks_uri }) || {}),
250
+ ...((c?.pkce === undefined &&
251
+ v?.code_challenge_methods_supported && { pkce: v.code_challenge_methods_supported.indexOf('S256') > -1 }) ||
252
+ {}),
253
+ ...{ scope: c.scope || 'openid' }
254
+ });
255
+ }
101
256
  }
102
- else {
103
- delete storage[storageKey];
257
+ };
258
+ const setExpires = (t) => {
259
+ const expiresIn = Number(t?.expires_in) || 0;
260
+ if (expiresIn && !t.expires) {
261
+ token.set({
262
+ ...t,
263
+ expires: Date.now() + expiresIn * 1000
264
+ });
104
265
  }
105
- }
266
+ };
267
+ let inFlight;
268
+ const checkToken = (t) => {
269
+ if (inFlight)
270
+ return inFlight;
271
+ inFlight = (async () => {
272
+ if (isExpiredToken(t)) {
273
+ await autoconfigOauth();
274
+ const refreshed = await refresh(t, config());
275
+ if (refreshed && !isExpiredToken(refreshed)) {
276
+ //keep the refresh token cuz we might not net a new one
277
+ setExpires({ refresh_token: t.refresh_token, ...refreshed });
278
+ }
279
+ }
280
+ else {
281
+ setExpires(t);
282
+ }
283
+ })().finally(() => (inFlight = undefined));
284
+ return inFlight;
285
+ };
286
+ effect(async () => {
287
+ const t = token();
288
+ await untracked(() => checkToken(t));
289
+ });
290
+ return {
291
+ token,
292
+ type,
293
+ accessToken,
294
+ status,
295
+ isAuthorized,
296
+ error,
297
+ hasError,
298
+ errorDescription,
299
+ storageKey,
300
+ checkToken,
301
+ autoconfigOauth
302
+ };
106
303
  }
107
- refreshToken(token) {
108
- const { tokenPath, clientId, clientSecret, scope } = this.authConfig.config;
109
- const { refresh_token } = token || {};
110
- return tokenPath && refresh_token && this.http.post(tokenPath, new HttpParams({
111
- fromObject: {
112
- client_id: clientId,
113
- ...clientSecret && { client_secret: clientSecret } || {},
114
- grant_type: 'refresh_token',
115
- refresh_token,
116
- ...scope && { scope } || {},
304
+ });
305
+
306
+ const getPath = (input) => input instanceof URL ? input.pathname : input instanceof Request ? new URL(input.url).pathname : input;
307
+ const isPathIgnored = (input) => ignorePaths().some(pattern => pattern.test(getPath(input)));
308
+ const ignorePaths = computed(() => oauthConfig().ignorePaths, ...(ngDevMode ? [{ debugName: "ignorePaths" }] : /* istanbul ignore next */ []));
309
+ const OAUTH_FETCH = new InjectionToken('OAUTH_FETCH', {
310
+ providedIn: 'root',
311
+ factory: () => {
312
+ const { token, accessToken, checkToken } = inject(OAUTH_TOKEN);
313
+ return async (input, init) => {
314
+ if (!isPathIgnored(input)) {
315
+ await checkToken(token());
316
+ const at = accessToken();
317
+ if (at) {
318
+ const headers = new Headers(init?.headers);
319
+ headers.set('Authorization', at);
320
+ if (!headers.has('Content-Type'))
321
+ headers.set('Content-Type', 'application/json');
322
+ const response = await globalThis.fetch(input, { ...init, headers });
323
+ if (response.status === 401) {
324
+ token.set(await response.json());
325
+ }
326
+ return response;
327
+ }
117
328
  }
118
- }), {
119
- headers: HEADER_APPLICATION
120
- }).pipe(catchError(() => {
121
- this.token = {};
122
- return of(this.token);
123
- }), map(token => {
124
- this.token = {
125
- ...this.token,
126
- ...token
127
- };
128
- return this.token;
129
- })) || of(token);
329
+ return globalThis.fetch(input, init);
330
+ };
331
+ }
332
+ });
333
+
334
+ const strictJwt = computed(() => oauthConfig().strictJwt, ...(ngDevMode ? [{ debugName: "strictJwt" }] : /* istanbul ignore next */ []));
335
+ const jwksUri = computed(() => config()?.jwksUri, ...(ngDevMode ? [{ debugName: "jwksUri" }] : /* istanbul ignore next */ []));
336
+ const jwt = (idToken) => {
337
+ const payload = idToken?.split('.')[1];
338
+ return payload
339
+ ? JSON.parse(decodeURIComponent(Array.from(atob(payload))
340
+ .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
341
+ .join('')))
342
+ : {};
343
+ };
344
+ let jwksSet;
345
+ const verifyJwt = async (idToken) => {
346
+ if (!idToken)
347
+ return {};
348
+ if (!jwksSet)
349
+ return jwt(idToken);
350
+ const { issuerPath, clientId } = config() || {};
351
+ try {
352
+ const { payload } = await jwtVerify(idToken, jwksSet, {
353
+ ...(issuerPath && { issuer: issuerPath }),
354
+ ...(clientId && { audience: clientId })
355
+ });
356
+ return payload;
357
+ }
358
+ catch {
359
+ return { error: 'Invalid token' };
130
360
  }
131
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthTokenService, deps: [{ token: OAuthConfig }, { token: OAuthHttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
132
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthTokenService, providedIn: 'root' }); }
133
- }
134
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthTokenService, decorators: [{
135
- type: Injectable,
136
- args: [{
137
- providedIn: 'root'
138
- }]
139
- }], ctorParameters: () => [{ type: OAuthConfig }, { type: OAuthHttpClient }] });
361
+ };
362
+ const OAUTH_VERIFY_JWT = new InjectionToken('OAUTH_VERIFY_JWT', {
363
+ providedIn: 'root',
364
+ factory: () => {
365
+ effect(() => {
366
+ const uri = jwksUri();
367
+ jwksSet = uri && strictJwt() ? createRemoteJWKSet(new URL(uri)) : undefined;
368
+ });
369
+ return verifyJwt;
370
+ }
371
+ });
372
+
373
+ const OAUTH_USER = new InjectionToken('OAUTH_USER', {
374
+ providedIn: 'root',
375
+ factory: () => {
376
+ const { token, isAuthorized, autoconfigOauth } = inject(OAUTH_TOKEN);
377
+ const verifyJwt = inject(OAUTH_VERIFY_JWT);
378
+ const userInfo = inject(OAUTH_USER_INFO);
379
+ const fetch = inject(OAUTH_FETCH);
380
+ return resource({
381
+ params: () => ({
382
+ idToken: token().id_token,
383
+ authorized: isAuthorized(),
384
+ userPath: config()?.userPath
385
+ }),
386
+ loader: async ({ params: { idToken, authorized, userPath } }) => {
387
+ if (idToken)
388
+ return verifyJwt(idToken);
389
+ if (authorized && userPath) {
390
+ await autoconfigOauth();
391
+ return userInfo({ userPath }, fetch);
392
+ }
393
+ return undefined;
394
+ }
395
+ });
396
+ }
397
+ });
140
398
 
141
399
  const arrToString = (buf) => buf.reduce((s, b) => s + String.fromCharCode(b), '');
142
- const base64url = (str) => btoa(str)
143
- .replace(/\+/g, '-')
144
- .replace(/\//g, '_')
145
- .replace(/=/g, '');
400
+ const base64url = (str) => btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
146
401
  const randomString = (length = 48) => {
147
402
  const buff = arrToString(crypto.getRandomValues(new Uint8Array(length * 2)));
148
403
  return base64url(buff).substring(0, length);
@@ -152,572 +407,135 @@ const pkce = async (value) => {
152
407
  return base64url(arrToString(new Uint8Array(buff)));
153
408
  };
154
409
  const parseOauthUri = (hash) => {
155
- const regex = /([^&=]+)=([^&]*)/g;
156
- const params = {};
157
- let m;
158
- // tslint:disable-next-line:no-conditional-assignment
159
- while ((m = regex.exec(hash)) !== null) {
160
- params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
161
- }
162
- return Object.keys(params).length && params || {};
410
+ const params = Object.fromEntries(new URLSearchParams(hash));
411
+ return (Object.keys(params).length && params) || {};
163
412
  };
164
- const jwt = (token) => JSON.parse(atob(token.split('.')[1]));
165
- class OAuthService {
166
- get storageKey() {
167
- return this.authConfig.storageKey;
168
- }
169
- set storageKey(storageKey) {
170
- if (storageKey) {
171
- this.authConfig.storageKey = storageKey;
172
- this.tokenService.token = this.tokenService.saved;
173
- }
174
- }
175
- get token() {
176
- return this.tokenService.token;
177
- }
178
- set token(token) {
179
- this.tokenService.token = token;
180
- }
181
- get config() {
182
- return this.authConfig.config;
183
- }
184
- set config(config) {
185
- if (config) {
186
- this.authConfig.config = {
187
- ...this.authConfig.config,
188
- ...config
189
- };
190
- }
191
- }
192
- constructor(authConfig, tokenService, http, locationService) {
193
- this.authConfig = authConfig;
194
- this.tokenService = tokenService;
195
- this.http = http;
196
- this.locationService = locationService;
197
- this.state$ = new ReplaySubject(1);
198
- this.config$ = of(this.config).pipe(filter(Boolean), filter(config => !!config?.clientId), map(config => config), switchMap$1(config => !config.issuerPath && of(config) || this.http.get(`${config.issuerPath}/.well-known/openid-configuration?client_id=${config.clientId}`).pipe(tap(v => this.config = {
199
- ...v.authorization_endpoint && { authorizePath: v.authorization_endpoint } || {},
200
- ...v.token_endpoint && { tokenPath: v.token_endpoint } || {},
201
- ...v.revocation_endpoint && { revokePath: v.revocation_endpoint } || {},
202
- ...v.code_challenge_methods_supported && { pkce: v.code_challenge_methods_supported.indexOf('S256') > -1 } || {},
203
- ...v.userinfo_endpoint && { userPath: v.userinfo_endpoint } || {},
204
- ...v.introspection_endpoint && { introspectionPath: v.introspection_endpoint } || {},
205
- ...v.end_session_endpoint && { logoutPath: v.end_session_endpoint } || {},
206
- ...{ scope: config.scope || 'openid' }
207
- }), map(() => this.config))), shareReplay(1));
208
- this.token$ = this.config$.pipe(tap(config => {
209
- const { hash, search, origin, pathname } = this.authConfig.location || {};
210
- const isImplicitRedirect = hash && /(access_token=)|(error=)/.test(hash);
211
- const isAuthCodeRedirect = search && /(code=)|(error=)/.test(search) || hash && /(code=)|(error=)/.test(hash);
212
- if (isImplicitRedirect) {
213
- const parameters = parseOauthUri(hash.substring(1));
214
- this.token = {
215
- ...parameters,
216
- type: OAuthType.IMPLICIT,
217
- };
218
- this.checkResponse(this.token, parameters);
413
+ const OAUTH = new InjectionToken('OAUTH', {
414
+ providedIn: 'root',
415
+ factory: () => {
416
+ const { token, status, type, isAuthorized, storageKey, autoconfigOauth } = inject(OAUTH_TOKEN);
417
+ const resourceOwnerLogin = inject(OAUTH_RESOURCE_OWNER);
418
+ const clientCredentialLogin = inject(OAUTH_CLIENT_CREDENTIAL);
419
+ const revoke = inject(OAUTH_REVOKE);
420
+ const authorize = inject(OAUTH_AUTHORIZE);
421
+ const verifyJwt = inject(OAUTH_VERIFY_JWT);
422
+ const state = signal(undefined, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
423
+ const login = async (parameters) => {
424
+ await autoconfigOauth();
425
+ if (!!parameters && parameters.password) {
426
+ token.set((await resourceOwnerLogin(parameters, config())) || {});
219
427
  }
220
- else if (isAuthCodeRedirect) {
221
- const parameters = parseOauthUri(search && search.substring(1) || hash && hash.substring(1) || '');
222
- if (!this.checkResponse(this.token, parameters)) {
223
- this.token = parameters;
224
- }
225
- else {
226
- const newParametersString = this.getCleanedUnSearchParameters();
227
- const { clientId, clientSecret, tokenPath, scope } = config;
228
- const { codeVerifier } = this.token || {}; //should be set by authorizationUrl construction
229
- this.http.post(tokenPath, new HttpParams({
230
- fromObject: {
231
- code: parameters?.['code'],
232
- client_id: clientId,
233
- ...clientSecret && { client_secret: clientSecret } || {},
234
- redirect_uri: `${origin}${pathname}`,
235
- grant_type: 'authorization_code',
236
- ...scope && { scope } || {},
237
- ...codeVerifier && { code_verifier: codeVerifier } || {}
238
- }
239
- }), { headers: HEADER_APPLICATION }).pipe().subscribe(token => {
240
- this.token = {
241
- ...token,
242
- type: OAuthType.AUTHORIZATION_CODE
243
- };
244
- this.locationService.replaceState(`${pathname}${newParametersString}`);
245
- });
246
- }
428
+ else if (!!parameters &&
429
+ parameters.redirectUri &&
430
+ parameters.responseType) {
431
+ await toAuthorizationUrl(parameters);
247
432
  }
248
- }), switchMap$1(() => this.tokenService.token$), shareReplay(1));
249
- this.status$ = this.token$.pipe(map(token => token.access_token && OAuthStatus.AUTHORIZED || token.error && OAuthStatus.DENIED || OAuthStatus.NOT_AUTHORIZED), shareReplay(1));
250
- this.userInfo$ = this.status$.pipe(filter(s => s === OAuthStatus.AUTHORIZED), map(() => this.config.userPath), filter(Boolean), switchMap$1(path => this.getUserInfo(path)), shareReplay(1));
251
- this.type$ = this.tokenService.type$;
252
- this.ignorePaths = this.authConfig.ignorePaths || [];
253
- }
254
- async login(parameters) {
255
- if (!!parameters && parameters.password) {
256
- await this.resourceLogin(parameters);
257
- }
258
- else if (!!parameters && parameters.redirectUri && parameters.responseType) {
259
- await this.toAuthorizationUrl(parameters);
260
- }
261
- else {
262
- await this.clientCredentialLogin();
263
- }
264
- }
265
- logout(useLogoutUrl) {
266
- this.revoke();
267
- this.token = {};
268
- const { logoutPath, logoutRedirectUri } = this.authConfig.config;
269
- if (useLogoutUrl && logoutPath) {
270
- const { origin, pathname } = this.authConfig.location || {};
271
- const currentPath = `${origin}${pathname}`;
272
- this.authConfig.location?.replace(`${logoutPath}?post_logout_redirect_uri=${logoutRedirectUri || currentPath}`);
273
- }
274
- }
275
- getUserInfo(path) {
276
- const { userPath } = this.config;
277
- return this.http.get(path || userPath);
278
- }
279
- revoke() {
280
- const { revokePath, clientId, clientSecret } = this.authConfig.config;
281
- if (revokePath) {
282
- const { access_token, refresh_token } = this.token || {};
283
- const toRevoke = [];
284
- if (access_token) {
285
- toRevoke.push({
286
- ...clientId && { client_id: clientId } || {},
287
- ...clientSecret && { client_secret: clientSecret } || {},
288
- token: access_token,
289
- token_type_hint: 'access_token'
290
- });
291
- }
292
- if (refresh_token) {
293
- toRevoke.push({
294
- ...clientId && { client_id: clientId } || {},
295
- ...clientSecret && { client_secret: clientSecret } || {},
296
- token: refresh_token,
297
- token_type_hint: 'refresh_token'
298
- });
433
+ else {
434
+ token.set((await clientCredentialLogin(config())) || {});
299
435
  }
300
- from(toRevoke).pipe(concatMap(o => of(o).pipe(delay(300))), // space request to avoid cancellation
301
- switchMap$1(o => this.http.post(revokePath, new HttpParams({ fromObject: o })))).subscribe(noop);
302
- }
303
- }
304
- clientCredentialLogin() {
305
- const { clientId, clientSecret, tokenPath, scope } = this.authConfig.config;
306
- return firstValueFrom(this.http.post(tokenPath, new HttpParams({
307
- fromObject: {
308
- client_id: clientId,
309
- client_secret: clientSecret,
310
- grant_type: OAuthType.CLIENT_CREDENTIAL,
311
- ...scope ? { scope } : {},
436
+ };
437
+ const logout = async (next, state) => {
438
+ await autoconfigOauth();
439
+ const { logoutPath, clientId, logoutRedirectUri } = config() || {};
440
+ const returnUrl = next || logoutRedirectUri;
441
+ if (returnUrl && logoutPath) {
442
+ const { id_token } = token();
443
+ const tokenHint = (id_token && `&id_token_hint=${id_token}`) || '';
444
+ const stateFwd = (state && `&state=${state}`) || '';
445
+ const logoutUrl = `${logoutPath}?client_id=${clientId}&post_logout_redirect_uri=${returnUrl}${tokenHint}${stateFwd}`;
446
+ token.set({});
447
+ globalThis.location?.replace(logoutUrl);
312
448
  }
313
- }), { headers: HEADER_APPLICATION }).pipe(catchError((err) => {
314
- this.token = err;
315
- return throwError(() => err);
316
- }), tap(params => {
317
- this.token = {
318
- ...params,
319
- type: OAuthType.CLIENT_CREDENTIAL,
320
- };
321
- })));
322
- }
323
- resourceLogin(parameters) {
324
- const { clientId, clientSecret, tokenPath, scope } = this.authConfig.config;
325
- const { username, password } = parameters;
326
- return firstValueFrom(this.http.post(tokenPath, new HttpParams({
327
- fromObject: {
328
- client_id: clientId,
329
- ...clientSecret && { client_secret: clientSecret } || {},
330
- grant_type: OAuthType.RESOURCE,
331
- ...scope && { scope } || {},
332
- username,
333
- password
449
+ else {
450
+ await revoke(token(), config());
451
+ token.set({});
334
452
  }
335
- }), { headers: HEADER_APPLICATION }).pipe(catchError(err => {
336
- this.token = err;
337
- return throwError(() => err);
338
- }), tap(params => {
339
- this.token = {
340
- ...params,
341
- type: OAuthType.RESOURCE,
342
- };
343
- })));
344
- }
345
- async toAuthorizationUrl(parameters) {
346
- const { config, location } = this.authConfig;
347
- let authorizationUrl = `${config.authorizePath}`;
348
- authorizationUrl += config.authorizePath.includes('?') && '&' || '?';
349
- authorizationUrl += `client_id=${config.clientId}`;
350
- authorizationUrl += `&redirect_uri=${encodeURIComponent(parameters.redirectUri)}`;
351
- authorizationUrl += `&response_type=${parameters.responseType}`;
352
- authorizationUrl += `&scope=${encodeURIComponent(config.scope || '')}`;
353
- authorizationUrl += `&state=${encodeURIComponent(parameters.state || '')}`;
354
- return location?.replace(`${authorizationUrl}${this.generateNonce(config)}${await this.generateCodeChallenge(config)}`);
355
- }
356
- async generateCodeChallenge(config) {
357
- if (config.pkce) {
358
- const codeVerifier = randomString();
359
- this.token = { ...this.token, codeVerifier };
360
- return `&code_challenge=${await pkce(codeVerifier)}&code_challenge_method=S256`;
361
- }
362
- return '';
363
- }
364
- generateNonce(config) {
365
- if (config && config.scope && config.scope.indexOf('openid') > -1) {
366
- const nonce = randomString(10);
367
- this.token = { ...this.token, nonce };
368
- return `&nonce=${nonce}`;
369
- }
370
- return '';
371
- }
372
- checkResponse(token, parameters) {
373
- this.emitState(parameters);
374
- this.cleanLocationHash();
375
- if (!parameters || parameters['error']) {
376
- return false;
377
- }
378
- if (token && token.nonce && parameters['access_token']) {
379
- const jwtToken = jwt(parameters['access_token']);
380
- return token.nonce === jwtToken.nonce;
381
- }
382
- return parameters['access_token'] || parameters['code'];
383
- }
384
- getCleanedUnSearchParameters() {
385
- const { search } = this.authConfig.location || {};
386
- let searchString = search && search.substring(1) || '';
387
- const hashKeys = ['code', 'state', 'error', 'error_description', 'session_state', 'scope', 'authuser', 'prompt', 'iss'];
388
- hashKeys.forEach(hashKey => {
389
- const re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?');
390
- searchString = searchString.replace(re, '');
391
- });
392
- return searchString.length && `?${searchString}` || '';
393
- }
394
- cleanLocationHash() {
395
- if (this.authConfig.location) {
396
- const { hash } = this.authConfig.location;
397
- let curHash = hash && hash.substring(1) || '';
398
- const hashKeys = [
399
- 'access_token',
400
- 'token_type',
401
- 'expires_in',
402
- 'scope',
403
- 'state',
404
- 'error',
405
- 'error_description',
406
- 'session_state',
407
- 'nonce',
408
- 'id_token',
409
- 'code',
410
- 'iss'
411
- ];
412
- hashKeys.forEach(hashKey => {
413
- const re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?');
414
- curHash = curHash.replace(re, '');
415
- });
416
- this.authConfig.location.hash = curHash;
417
- }
418
- }
419
- emitState(parameters) {
420
- const { state } = parameters || {};
421
- state && this.state$.next(state);
422
- }
423
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthService, deps: [{ token: OAuthConfig }, { token: OAuthTokenService }, { token: i3.HttpClient }, { token: i2.Location }], target: i0.ɵɵFactoryTarget.Injectable }); }
424
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthService, providedIn: 'root' }); }
425
- }
426
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthService, decorators: [{
427
- type: Injectable,
428
- args: [{
429
- providedIn: 'root',
430
- }]
431
- }], ctorParameters: () => [{ type: OAuthConfig }, { type: OAuthTokenService }, { type: i3.HttpClient }, { type: i2.Location }] });
432
-
433
- const OAuthInterceptor = (req, next) => {
434
- const authConfig = inject(OAuthConfig);
435
- const tokenService = inject(OAuthTokenService);
436
- const isPathExcepted = (req) => {
437
- const { ignorePaths } = authConfig || {};
438
- if (ignorePaths) {
439
- for (const ignorePath of ignorePaths) {
440
- try {
441
- if (req.url.match(ignorePath)) {
442
- return true;
443
- }
444
- }
445
- catch (err) {
453
+ };
454
+ const oauthCallback = async (url) => {
455
+ const checkNonce = async (parameters) => {
456
+ if (parameters['error'])
457
+ return parameters;
458
+ const payload = await verifyJwt(parameters['id_token']);
459
+ if (payload?.error || payload?.nonce !== token()?.nonce) {
460
+ return { error: payload?.error || 'Invalid nonce' };
446
461
  }
447
- }
448
- }
449
- return false;
450
- };
451
- return isPathExcepted(req) && next(req) || tokenService.token$.pipe(take(1), map(token => {
452
- if (token?.access_token) {
453
- req = req.clone({
454
- setHeaders: {
455
- Authorization: `${token.token_type} ${token.access_token}`
462
+ return parameters;
463
+ };
464
+ const checkCode = async () => {
465
+ const parameters = await authorize(token(), config());
466
+ if (parameters) {
467
+ token.set(await checkNonce(parameters));
456
468
  }
457
- });
458
- }
459
- return req;
460
- }), switchMap(req => next(req)), catchError((err) => {
461
- if (err.status === 401) {
462
- tokenService.token = {
463
- error: `${err.status}`,
464
- error_description: err.message
465
469
  };
466
- }
467
- return throwError(() => err);
468
- }));
469
- };
470
-
471
- class OAuthLoginComponent {
472
- #redirectUri;
473
- #responseType;
474
- #i18n;
475
- get i18n() {
476
- return this.#i18n;
477
- }
478
- set i18n(i18n) {
479
- this.#i18n = {
480
- ...this.#i18n,
481
- ...i18n
470
+ const path = (url && new URL(url)) || globalThis.location || {};
471
+ const { hash, search } = path;
472
+ const isImplicitRedirect = hash && /(access_token=)|(error=)/.test(hash);
473
+ const isAuthCodeRedirect = (search && /(code=)|(error=)/.test(search)) || (hash && /(code=)|(error=)/.test(hash));
474
+ if (isImplicitRedirect) {
475
+ const parameters = parseOauthUri(hash.substring(1));
476
+ token.set({
477
+ ...(await checkNonce(parameters)),
478
+ type: OAuthType.IMPLICIT
479
+ });
480
+ state.set(parameters?.['state']);
481
+ }
482
+ else if (isAuthCodeRedirect) {
483
+ const parameters = parseOauthUri(search?.substring(1) || hash?.substring(1));
484
+ token.set({
485
+ ...token(),
486
+ ...parameters
487
+ // do not set type yet. will be set by authorize function since it is a two-step process
488
+ });
489
+ state.set(parameters?.['state']);
490
+ await autoconfigOauth();
491
+ await checkCode();
492
+ }
482
493
  };
483
- }
484
- get redirectUri() {
485
- return this.#redirectUri || `${globalThis.location?.origin}${this.locationService.path(true) || '/'}`;
486
- }
487
- set redirectUri(redirectUri) {
488
- if (redirectUri) {
489
- this.#redirectUri = redirectUri;
490
- }
491
- }
492
- set responseType(responseType) {
493
- if (this.responseType) {
494
- this.#responseType = responseType;
495
- }
496
- }
497
- get responseType() {
498
- return this.#responseType || this.type;
499
- }
500
- constructor(oauthService, locationService) {
501
- this.oauthService = oauthService;
502
- this.locationService = locationService;
503
- this.#i18n = {
504
- username: 'Username',
505
- password: 'Password',
506
- submit: 'Sign in',
507
- notAuthorized: 'Sign in',
508
- authorized: 'Welcome',
509
- denied: 'Access Denied. Try again!'
494
+ const toAuthorizationUrl = async (parameters) => {
495
+ const { authorizePath, clientId, scope = '', pkce } = config();
496
+ let authorizationUrl = `${authorizePath}`;
497
+ authorizationUrl += (authorizePath.includes('?') && '&') || '?';
498
+ authorizationUrl += `client_id=${clientId}`;
499
+ token.set({ ...token(), redirect_uri: parameters.redirectUri });
500
+ authorizationUrl += `&access_type=${parameters.accessType || 'offline'}`;
501
+ authorizationUrl += `&prompt=${parameters.prompt || ''}`;
502
+ authorizationUrl += `&redirect_uri=${encodeURIComponent(parameters.redirectUri)}`;
503
+ authorizationUrl += `&response_type=${parameters.responseType}`;
504
+ authorizationUrl += `&scope=${encodeURIComponent(scope)}`;
505
+ authorizationUrl += `&state=${encodeURIComponent(parameters.state || '')}`;
506
+ authorizationUrl = `${authorizationUrl}${generateNonce(scope)}${await generateCodeChallenge(pkce)}`;
507
+ return globalThis.location?.replace(authorizationUrl);
510
508
  };
511
- this.type = OAuthType.RESOURCE;
512
- this.useLogoutUrl = false;
513
- this.state = '';
514
- this.stateChange = this.oauthService.state$.asObservable();
515
- this.loginTemplate = null;
516
- this.username = '';
517
- this.password = '';
518
- this.OAuthStatus = OAuthStatus;
519
- this.OAuthType = OAuthType;
520
- this.collapse = false;
521
- this.status$ = this.oauthService.status$.pipe(tap(s => {
522
- if (s === OAuthStatus.AUTHORIZED && this.profileName$) {
523
- this.profileName$.pipe(take(1)).subscribe(n => this.profileName = n);
509
+ const generateNonce = (scope) => {
510
+ if (scope.indexOf('openid') > -1) {
511
+ const nonce = randomString();
512
+ token.set({ ...token(), nonce });
513
+ return `&nonce=${nonce}`;
524
514
  }
525
- else {
526
- const { token } = this.oauthService;
527
- const userInfo = token && token.id_token && JSON.parse(atob(token.id_token.split('.')[1])) || {};
528
- this.profileName = userInfo.name || userInfo.username || userInfo.email || userInfo.sub || '';
515
+ return '';
516
+ };
517
+ const generateCodeChallenge = async (doPkce) => {
518
+ if (doPkce) {
519
+ const code_verifier = randomString();
520
+ token.set({ ...token(), code_verifier });
521
+ return `&code_challenge=${await pkce(code_verifier)}&code_challenge_method=S256`;
529
522
  }
530
- }));
531
- this.loginFunction = (p) => this.login(p);
532
- this.logoutFunction = () => this.logout();
533
- }
534
- logout() {
535
- this.oauthService.logout(this.useLogoutUrl);
536
- }
537
- login(parameters) {
538
- this.collapse = false;
539
- return this.oauthService.login(parameters);
540
- }
541
- toggleCollapse() {
542
- this.collapse = !this.collapse;
543
- }
544
- keyboardEvent() {
545
- this.collapse = false;
523
+ return '';
524
+ };
525
+ return {
526
+ login,
527
+ logout,
528
+ oauthCallback,
529
+ state,
530
+ token,
531
+ status,
532
+ type,
533
+ isAuthorized,
534
+ config,
535
+ storageKey
536
+ };
546
537
  }
547
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthLoginComponent, deps: [{ token: OAuthService }, { token: i2.Location }], target: i0.ɵɵFactoryTarget.Component }); }
548
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.3", type: OAuthLoginComponent, isStandalone: true, selector: "oauth-login", inputs: { type: "type", i18n: "i18n", redirectUri: "redirectUri", responseType: "responseType", useLogoutUrl: "useLogoutUrl", state: "state", profileName$: "profileName$" }, outputs: { stateChange: "stateChange" }, host: { listeners: { "window:keydown.escape": "keyboardEvent()" } }, queries: [{ propertyName: "loginTemplate", first: true, predicate: ["login"], descendants: true }], ngImport: i0, template: `
549
- @if (loginTemplate) {
550
- <ng-container
551
- [ngTemplateOutlet]="loginTemplate"
552
- [ngTemplateOutletContext]="{login: loginFunction, logout: logoutFunction, status: status$ | async}">
553
- </ng-container>
554
- } @else {
555
- @if (status$ | async; as status) {
556
- @if (type === OAuthType.RESOURCE) {
557
- <div class="oauth dropdown text-end {{collapse ? 'show': ''}}">
558
- <button class="btn btn-link p-0 dropdown-toggle"
559
- (click)="status === OAuthStatus.AUTHORIZED ? logout() : toggleCollapse()">
560
- <ng-container *ngTemplateOutlet="message"></ng-container>
561
- </button>
562
- <div class="dropdown-menu mr-3 {{collapse ? 'show': ''}}">
563
- @if (status === OAuthStatus.NOT_AUTHORIZED || status === OAuthStatus.DENIED) {
564
- <form class="p-3"
565
- #form="ngForm"
566
- (submit)="login({username: username, password: password})">
567
- <div class="mb-3">
568
- <input type="text"
569
- class="form-control"
570
- name="username"
571
- required
572
- [(ngModel)]="username"
573
- [placeholder]="i18n.username">
574
- </div>
575
- <div class="mb-3">
576
- <input type="password"
577
- class="form-control"
578
- name="password"
579
- required
580
- [(ngModel)]="password"
581
- [placeholder]="i18n.password">
582
- </div>
583
- <div class="text-end">
584
- <button type="submit"
585
- class="btn btn-primary"
586
- [disabled]="form.invalid">{{i18n.submit}}</button>
587
- </div>
588
- </form>
589
- }
590
- </div>
591
- </div>
592
- } @else {
593
- <a role="button"
594
- class="oauth"
595
- (click)="status === OAuthStatus.AUTHORIZED ? logout() : login({responseType: responseType, redirectUri: redirectUri, state:state})">
596
- <ng-container *ngTemplateOutlet="message"></ng-container>
597
- </a>
598
- }
599
- <ng-template #message>
600
- @if (status === OAuthStatus.NOT_AUTHORIZED) {
601
- <span class="not-authorized"
602
- [innerHTML]="i18n.notAuthorized"></span>
603
- }
604
- @if (status === OAuthStatus.AUTHORIZED) {
605
- <span class="authorized"
606
- >
607
- <span class="welcome" [innerHTML]="i18n.authorized + '&nbsp;'"></span>
608
- <strong class="profile-name"
609
- [innerHTML]="profileName"></strong>
610
- </span>
611
- }
612
- @if (status === OAuthStatus.DENIED) {
613
- <span class="denied"
614
- [innerHTML]="i18n.denied"></span>
615
- }
616
- </ng-template>
617
- }
618
- }
619
- `, isInline: true, styles: [".oauth .dropdown-menu{left:auto;right:0;box-shadow:0 5px 10px #0003;min-width:250px}.oauth .dropdown-menu:before{content:\"\";display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:#0003;position:absolute;top:-7px;left:auto;right:15px}.oauth .dropdown-menu:after{content:\"\";display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:auto;right:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], encapsulation: i0.ViewEncapsulation.None }); }
620
- }
621
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: OAuthLoginComponent, decorators: [{
622
- type: Component,
623
- args: [{ selector: 'oauth-login', standalone: true, imports: [
624
- CommonModule,
625
- FormsModule
626
- ], template: `
627
- @if (loginTemplate) {
628
- <ng-container
629
- [ngTemplateOutlet]="loginTemplate"
630
- [ngTemplateOutletContext]="{login: loginFunction, logout: logoutFunction, status: status$ | async}">
631
- </ng-container>
632
- } @else {
633
- @if (status$ | async; as status) {
634
- @if (type === OAuthType.RESOURCE) {
635
- <div class="oauth dropdown text-end {{collapse ? 'show': ''}}">
636
- <button class="btn btn-link p-0 dropdown-toggle"
637
- (click)="status === OAuthStatus.AUTHORIZED ? logout() : toggleCollapse()">
638
- <ng-container *ngTemplateOutlet="message"></ng-container>
639
- </button>
640
- <div class="dropdown-menu mr-3 {{collapse ? 'show': ''}}">
641
- @if (status === OAuthStatus.NOT_AUTHORIZED || status === OAuthStatus.DENIED) {
642
- <form class="p-3"
643
- #form="ngForm"
644
- (submit)="login({username: username, password: password})">
645
- <div class="mb-3">
646
- <input type="text"
647
- class="form-control"
648
- name="username"
649
- required
650
- [(ngModel)]="username"
651
- [placeholder]="i18n.username">
652
- </div>
653
- <div class="mb-3">
654
- <input type="password"
655
- class="form-control"
656
- name="password"
657
- required
658
- [(ngModel)]="password"
659
- [placeholder]="i18n.password">
660
- </div>
661
- <div class="text-end">
662
- <button type="submit"
663
- class="btn btn-primary"
664
- [disabled]="form.invalid">{{i18n.submit}}</button>
665
- </div>
666
- </form>
667
- }
668
- </div>
669
- </div>
670
- } @else {
671
- <a role="button"
672
- class="oauth"
673
- (click)="status === OAuthStatus.AUTHORIZED ? logout() : login({responseType: responseType, redirectUri: redirectUri, state:state})">
674
- <ng-container *ngTemplateOutlet="message"></ng-container>
675
- </a>
676
- }
677
- <ng-template #message>
678
- @if (status === OAuthStatus.NOT_AUTHORIZED) {
679
- <span class="not-authorized"
680
- [innerHTML]="i18n.notAuthorized"></span>
681
- }
682
- @if (status === OAuthStatus.AUTHORIZED) {
683
- <span class="authorized"
684
- >
685
- <span class="welcome" [innerHTML]="i18n.authorized + '&nbsp;'"></span>
686
- <strong class="profile-name"
687
- [innerHTML]="profileName"></strong>
688
- </span>
689
- }
690
- @if (status === OAuthStatus.DENIED) {
691
- <span class="denied"
692
- [innerHTML]="i18n.denied"></span>
693
- }
694
- </ng-template>
695
- }
696
- }
697
- `, encapsulation: ViewEncapsulation.None, styles: [".oauth .dropdown-menu{left:auto;right:0;box-shadow:0 5px 10px #0003;min-width:250px}.oauth .dropdown-menu:before{content:\"\";display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:#0003;position:absolute;top:-7px;left:auto;right:15px}.oauth .dropdown-menu:after{content:\"\";display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:auto;right:16px}\n"] }]
698
- }], ctorParameters: () => [{ type: OAuthService }, { type: i2.Location }], propDecorators: { type: [{
699
- type: Input
700
- }], i18n: [{
701
- type: Input
702
- }], redirectUri: [{
703
- type: Input
704
- }], responseType: [{
705
- type: Input
706
- }], useLogoutUrl: [{
707
- type: Input
708
- }], state: [{
709
- type: Input
710
- }], stateChange: [{
711
- type: Output
712
- }], profileName$: [{
713
- type: Input
714
- }], loginTemplate: [{
715
- type: ContentChild,
716
- args: ['login', { static: false }]
717
- }], keyboardEvent: [{
718
- type: HostListener,
719
- args: ['window:keydown.escape']
720
- }] } });
538
+ });
721
539
 
722
540
  /*
723
541
  * Public API Surface of ngx-oauth
@@ -727,5 +545,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImpor
727
545
  * Generated bundle index. Do not edit.
728
546
  */
729
547
 
730
- export { HEADER_APPLICATION, OAUTH_CONFIG, OAuthConfig, OAuthHttpClient, OAuthInterceptor, OAuthLoginComponent, OAuthService, OAuthStatus, OAuthTokenService, OAuthType, defaultOAuthConfig, provideOAuthConfig, provideOAuthConfigFactory };
548
+ export { OAUTH, OAUTH_AUTHORIZE, OAUTH_CLIENT_CREDENTIAL, OAUTH_FETCH, OAUTH_INTROSPECT, OAUTH_OPENID_CONFIG, OAUTH_REFRESH, OAUTH_RESOURCE_OWNER, OAUTH_REVOKE, OAUTH_TOKEN, OAUTH_USER, OAUTH_USER_INFO, OAUTH_VERIFY_JWT, OAuthStatus, OAuthType, provideOAuthConfig };
731
549
  //# sourceMappingURL=ngx-oauth.mjs.map