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.
- package/README.md +29 -195
- package/fesm2022/ngx-oauth-component.mjs +272 -0
- package/fesm2022/ngx-oauth-component.mjs.map +1 -0
- package/fesm2022/ngx-oauth.mjs +487 -669
- package/fesm2022/ngx-oauth.mjs.map +1 -1
- package/package.json +15 -9
- package/types/ngx-oauth-component.d.ts +51 -0
- package/types/ngx-oauth.d.ts +165 -0
- package/components/index.d.ts +0 -1
- package/components/o-auth-login.component.d.ts +0 -47
- package/config/index.d.ts +0 -17
- package/esm2022/components/index.mjs +0 -2
- package/esm2022/components/o-auth-login.component.mjs +0 -261
- package/esm2022/config/index.mjs +0 -32
- package/esm2022/index.mjs +0 -8
- package/esm2022/models/index.mjs +0 -16
- package/esm2022/ngx-oauth.mjs +0 -5
- package/esm2022/services/index.mjs +0 -5
- package/esm2022/services/o-auth-http-client.mjs +0 -15
- package/esm2022/services/o-auth-token.service.mjs +0 -80
- package/esm2022/services/o-auth.interceptor.mjs +0 -43
- package/esm2022/services/o-auth.service.mjs +0 -302
- package/index.d.ts +0 -4
- package/models/index.d.ts +0 -100
- package/services/index.d.ts +0 -4
- package/services/o-auth-http-client.d.ts +0 -6
- package/services/o-auth-token.service.d.ts +0 -21
- package/services/o-auth.interceptor.d.ts +0 -2
- package/services/o-auth.service.d.ts +0 -42
package/fesm2022/ngx-oauth.mjs
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
81
|
-
return
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
82
38
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
156
|
-
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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 (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
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 + ' '"></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 + ' '"></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 {
|
|
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
|