ngx-oauth 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -9
- package/esm2020/lib/components/login/oauth-login.component.mjs +25 -13
- package/esm2020/lib/models/index.mjs +2 -1
- package/esm2020/lib/oauth.module.mjs +2 -3
- package/esm2020/lib/services/oauth.interceptor.mjs +11 -8
- package/esm2020/lib/services/oauth.service.mjs +129 -83
- package/fesm2015/ngx-oauth.mjs +169 -111
- package/fesm2015/ngx-oauth.mjs.map +1 -1
- package/fesm2020/ngx-oauth.mjs +164 -104
- package/fesm2020/ngx-oauth.mjs.map +1 -1
- package/lib/components/login/oauth-login.component.d.ts +10 -6
- package/lib/models/index.d.ts +38 -1
- package/lib/services/oauth.service.d.ts +19 -4
- package/package.json +1 -1
package/fesm2020/ngx-oauth.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, Inject, EventEmitter, Component, Input, Output, ContentChild, HostListener, PLATFORM_ID, Optional, NgModule } from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injectable, Inject, EventEmitter, Component, ViewEncapsulation, Input, Output, ContentChild, HostListener, PLATFORM_ID, Optional, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
4
|
import { HttpHeaders, HttpParams, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
|
5
|
-
import {
|
|
6
|
-
import { ReplaySubject, EMPTY, from,
|
|
5
|
+
import { filter, map, switchMap, shareReplay, tap, catchError, concatMap, delay } from 'rxjs/operators';
|
|
6
|
+
import { ReplaySubject, of, EMPTY, from, noop, throwError, Subscription, take } from 'rxjs';
|
|
7
7
|
import * as i2 from '@angular/common';
|
|
8
8
|
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
9
9
|
import * as i3 from '@angular/forms';
|
|
@@ -15,6 +15,7 @@ const SERVER_PATH = new InjectionToken('SERVER_PATH');
|
|
|
15
15
|
const LOCATION = new InjectionToken('Location');
|
|
16
16
|
const STORAGE = new InjectionToken('Storage');
|
|
17
17
|
const OAUTH_CONFIG = new InjectionToken('OAuthConfig');
|
|
18
|
+
const OAUTH_TOKEN = new InjectionToken('OAuthToken');
|
|
18
19
|
var OAuthType;
|
|
19
20
|
(function (OAuthType) {
|
|
20
21
|
OAuthType["RESOURCE"] = "password";
|
|
@@ -56,49 +57,78 @@ const parseOauthUri = (hash) => {
|
|
|
56
57
|
}
|
|
57
58
|
return null;
|
|
58
59
|
};
|
|
60
|
+
const jwt = (token) => JSON.parse(atob(token.split('.')[1]));
|
|
59
61
|
class OAuthService {
|
|
60
|
-
constructor(http, zone, authConfig, locationService) {
|
|
62
|
+
constructor(http, zone, authConfig, location, locationService) {
|
|
61
63
|
this.http = http;
|
|
62
64
|
this.zone = zone;
|
|
63
65
|
this.authConfig = authConfig;
|
|
66
|
+
this.location = location;
|
|
64
67
|
this.locationService = locationService;
|
|
65
68
|
this._token = null;
|
|
66
69
|
this._status = OAuthStatus.NOT_AUTHORIZED;
|
|
67
70
|
this.state$ = new ReplaySubject(1);
|
|
68
71
|
this.status$ = new ReplaySubject(1);
|
|
69
|
-
this.
|
|
72
|
+
this.userInfo$ = this.status$.pipe(filter(s => s === OAuthStatus.AUTHORIZED), map(() => {
|
|
73
|
+
const { config } = this.authConfig;
|
|
74
|
+
return config.userPath;
|
|
75
|
+
}), filter(p => !!p), switchMap(path => this.http.get(path)), shareReplay());
|
|
76
|
+
setTimeout(() => this.init()); // decouple for http interceptor
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the oauth config for initialize. If OpenId with issuerPath is configured then configure from server openid configuration.
|
|
80
|
+
* @protected
|
|
81
|
+
*/
|
|
82
|
+
get config$() {
|
|
83
|
+
let { config } = this.authConfig;
|
|
84
|
+
if (config && config.clientId) {
|
|
85
|
+
const { issuerPath, scope } = config;
|
|
86
|
+
if (issuerPath) {
|
|
87
|
+
return this.http.get(`${issuerPath}/.well-known/openid-configuration`).pipe(tap(v => this.set(this.type, {
|
|
88
|
+
...v.authorization_endpoint && { authorizePath: v.authorization_endpoint } || {},
|
|
89
|
+
...v.token_endpoint && { tokenPath: v.token_endpoint } || {},
|
|
90
|
+
...v.revocation_endpoint && { revokePath: v.revocation_endpoint } || {},
|
|
91
|
+
...v.code_challenge_methods_supported && { pkce: v.code_challenge_methods_supported.indexOf('S256') > -1 } || {},
|
|
92
|
+
...v.userinfo_endpoint && { userPath: v.userinfo_endpoint } || {},
|
|
93
|
+
...v.introspection_endpoint && { introspectionPath: v.introspection_endpoint } || {},
|
|
94
|
+
...scope && {} || { scope: 'openid' }
|
|
95
|
+
})), map(() => this.authConfig.config));
|
|
96
|
+
}
|
|
97
|
+
return of(config);
|
|
98
|
+
}
|
|
99
|
+
console.warn('clientId is missing in oauth config');
|
|
100
|
+
return EMPTY;
|
|
70
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Init. Will check the url implicit or authorization flow or existing saved token.
|
|
104
|
+
* @protected
|
|
105
|
+
*/
|
|
71
106
|
init() {
|
|
72
|
-
const { hash, search, origin, pathname } = this.
|
|
73
|
-
const isImplicitRedirect = hash && /(
|
|
107
|
+
const { hash, search, origin, pathname } = this.location;
|
|
108
|
+
const isImplicitRedirect = hash && /(access_token=)|(error=)/.test(hash);
|
|
74
109
|
const isAuthCodeRedirect = search && /(code=)|(error=)/.test(search);
|
|
75
110
|
const { storageKey } = this.authConfig;
|
|
76
111
|
const savedToken = storageKey && this.authConfig.storage && this.authConfig.storage[storageKey] &&
|
|
77
112
|
JSON.parse(this.authConfig.storage[storageKey]);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.cleanLocationHash();
|
|
82
|
-
if (!parameters || parameters.error) {
|
|
83
|
-
this.token = null;
|
|
84
|
-
this.status = OAuthStatus.DENIED;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
113
|
+
this.config$.subscribe(config => {
|
|
114
|
+
if (isImplicitRedirect) {
|
|
115
|
+
const parameters = parseOauthUri(hash.substr(1));
|
|
87
116
|
this.token = parameters;
|
|
88
|
-
this.status = OAuthStatus.AUTHORIZED;
|
|
117
|
+
this.status = this.checkResponse(savedToken, parameters) && OAuthStatus.AUTHORIZED || OAuthStatus.DENIED;
|
|
89
118
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
else if (isAuthCodeRedirect) {
|
|
120
|
+
const parameters = parseOauthUri(search.substr(1));
|
|
121
|
+
if (!this.checkResponse(savedToken, parameters)) {
|
|
122
|
+
this.token = parameters;
|
|
123
|
+
this.status = OAuthStatus.DENIED;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const newParametersString = this.getCleanedUnSearchParameters();
|
|
127
|
+
const { clientId, clientSecret, tokenPath, scope } = config;
|
|
128
|
+
const codeVerifier = savedToken && savedToken.codeVerifier;
|
|
99
129
|
this.http.post(tokenPath, new HttpParams({
|
|
100
130
|
fromObject: {
|
|
101
|
-
code: parameters
|
|
131
|
+
code: parameters?.code,
|
|
102
132
|
client_id: clientId,
|
|
103
133
|
...clientSecret && { client_secret: clientSecret } || {},
|
|
104
134
|
redirect_uri: `${origin}${pathname}${newParametersString}`,
|
|
@@ -106,54 +136,47 @@ class OAuthService {
|
|
|
106
136
|
...scope && { scope } || {},
|
|
107
137
|
...codeVerifier && { code_verifier: codeVerifier } || {}
|
|
108
138
|
}
|
|
109
|
-
}), { headers: REQUEST_HEADER }).pipe(catchError(() => {
|
|
110
|
-
this.token =
|
|
139
|
+
}), { headers: REQUEST_HEADER }).pipe(catchError((err) => {
|
|
140
|
+
this.token = err;
|
|
111
141
|
this.status = OAuthStatus.DENIED;
|
|
112
|
-
this.locationService.
|
|
142
|
+
this.locationService.replaceState(`${pathname}${newParametersString}`);
|
|
113
143
|
return EMPTY;
|
|
114
144
|
})).subscribe(token => {
|
|
115
145
|
this.token = token;
|
|
116
|
-
|
|
117
|
-
this.locationService.
|
|
146
|
+
this.status = OAuthStatus.AUTHORIZED;
|
|
147
|
+
this.locationService.replaceState(`${pathname}${newParametersString}`);
|
|
118
148
|
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
this.token = null;
|
|
123
|
-
this.status = OAuthStatus.DENIED;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else if (savedToken) {
|
|
127
|
-
const { access_token, refresh_token, error } = savedToken;
|
|
128
|
-
if (error) {
|
|
129
|
-
this.token = null;
|
|
130
|
-
this.status = OAuthStatus.DENIED;
|
|
149
|
+
}
|
|
131
150
|
}
|
|
132
|
-
else if (
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
else if (savedToken) {
|
|
152
|
+
this._token = savedToken;
|
|
153
|
+
const { access_token, refresh_token, error } = savedToken;
|
|
154
|
+
if (access_token) {
|
|
155
|
+
if (refresh_token) { // force refresh since might be a manual page refresh
|
|
136
156
|
this.refreshToken();
|
|
137
|
-
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.status = OAuthStatus.AUTHORIZED;
|
|
160
|
+
}
|
|
138
161
|
}
|
|
139
162
|
else {
|
|
140
|
-
this.status = OAuthStatus.
|
|
163
|
+
this.status = error && OAuthStatus.DENIED || OAuthStatus.NOT_AUTHORIZED;
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
166
|
+
else {
|
|
167
|
+
this.status = OAuthStatus.NOT_AUTHORIZED;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
147
170
|
}
|
|
148
|
-
login(parameters) {
|
|
171
|
+
async login(parameters) {
|
|
149
172
|
if (this.isResourceType(parameters)) {
|
|
150
173
|
this.resourceLogin(parameters);
|
|
151
174
|
}
|
|
152
175
|
else if (this.isAuthorizationCodeType(parameters)) {
|
|
153
|
-
this.authorizationCodeLogin(parameters)
|
|
176
|
+
await this.authorizationCodeLogin(parameters);
|
|
154
177
|
}
|
|
155
178
|
else if (this.isImplicitType(parameters)) {
|
|
156
|
-
this.implicitLogin(parameters)
|
|
179
|
+
await this.implicitLogin(parameters);
|
|
157
180
|
}
|
|
158
181
|
else if (this.isClientCredentialType()) {
|
|
159
182
|
this.clientCredentialLogin();
|
|
@@ -217,14 +240,14 @@ class OAuthService {
|
|
|
217
240
|
this.http.post(tokenPath, new HttpParams({
|
|
218
241
|
fromObject: {
|
|
219
242
|
client_id: clientId,
|
|
220
|
-
client_secret: clientSecret,
|
|
243
|
+
...clientSecret && { client_secret: clientSecret } || {},
|
|
221
244
|
grant_type: OAuthType.RESOURCE,
|
|
222
|
-
...scope
|
|
245
|
+
...scope && { scope } || {},
|
|
223
246
|
username,
|
|
224
247
|
password
|
|
225
248
|
}
|
|
226
|
-
}), { headers: REQUEST_HEADER }).pipe(catchError(
|
|
227
|
-
this.token =
|
|
249
|
+
}), { headers: REQUEST_HEADER }).pipe(catchError(err => {
|
|
250
|
+
this.token = err;
|
|
228
251
|
this.status = OAuthStatus.DENIED;
|
|
229
252
|
return EMPTY;
|
|
230
253
|
})).subscribe(params => {
|
|
@@ -234,11 +257,11 @@ class OAuthService {
|
|
|
234
257
|
}
|
|
235
258
|
async authorizationCodeLogin(parameters) {
|
|
236
259
|
const authUrl = await this.toAuthorizationUrl(parameters, OAuthType.AUTHORIZATION_CODE);
|
|
237
|
-
this.
|
|
260
|
+
this.location.replace(authUrl);
|
|
238
261
|
}
|
|
239
262
|
async implicitLogin(parameters) {
|
|
240
263
|
const authUrl = await this.toAuthorizationUrl(parameters, OAuthType.IMPLICIT);
|
|
241
|
-
this.
|
|
264
|
+
this.location.replace(authUrl);
|
|
242
265
|
}
|
|
243
266
|
clientCredentialLogin() {
|
|
244
267
|
const { clientId, clientSecret, tokenPath, scope } = this.authConfig.config;
|
|
@@ -272,19 +295,42 @@ class OAuthService {
|
|
|
272
295
|
}
|
|
273
296
|
async toAuthorizationUrl(parameters, responseType) {
|
|
274
297
|
const { config } = this.authConfig;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
298
|
+
let authorizationUrl = `${config.authorizePath}`;
|
|
299
|
+
authorizationUrl += config.authorizePath.includes('?') && '&' || '?';
|
|
300
|
+
authorizationUrl += `client_id=${config.clientId}`;
|
|
301
|
+
authorizationUrl += `&redirect_uri=${encodeURIComponent(parameters.redirectUri)}`;
|
|
302
|
+
authorizationUrl += `&response_type=${responseType}`;
|
|
303
|
+
authorizationUrl += `&scope=${encodeURIComponent(config.scope || '')}`;
|
|
304
|
+
authorizationUrl += `&state=${encodeURIComponent(parameters.state || '')}`;
|
|
305
|
+
return `${authorizationUrl}${this.generateNonce(config)}${await this.generateCodeChallenge(config)}`;
|
|
306
|
+
}
|
|
307
|
+
async generateCodeChallenge(config) {
|
|
308
|
+
if (config.pkce) {
|
|
309
|
+
const codeVerifier = randomString();
|
|
310
|
+
this.token = { ...this.token, codeVerifier };
|
|
311
|
+
return `&code_challenge=${await pkce(codeVerifier)}&code_challenge_method=S256`;
|
|
312
|
+
}
|
|
313
|
+
return '';
|
|
314
|
+
}
|
|
315
|
+
generateNonce(config) {
|
|
316
|
+
if (config && config.scope && config.scope.indexOf('openid') > -1) {
|
|
317
|
+
const nonce = randomString(10);
|
|
318
|
+
this.token = { ...this.token, nonce };
|
|
319
|
+
return `&nonce=${nonce}`;
|
|
284
320
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
checkResponse(token, parameters) {
|
|
324
|
+
this.emitState(parameters);
|
|
325
|
+
this.cleanLocationHash();
|
|
326
|
+
if (!parameters || parameters.error) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (token && token.nonce && parameters.access_token) {
|
|
330
|
+
const jwtToken = jwt(parameters.access_token);
|
|
331
|
+
return token.nonce === jwtToken.nonce;
|
|
332
|
+
}
|
|
333
|
+
return parameters.access_token || parameters.code;
|
|
288
334
|
}
|
|
289
335
|
set token(token) {
|
|
290
336
|
this._token = token;
|
|
@@ -318,10 +364,10 @@ class OAuthService {
|
|
|
318
364
|
this.http.post(tokenPath, new HttpParams({
|
|
319
365
|
fromObject: {
|
|
320
366
|
client_id: clientId,
|
|
321
|
-
...clientSecret
|
|
367
|
+
...clientSecret && { client_secret: clientSecret } || {},
|
|
322
368
|
grant_type: 'refresh_token',
|
|
323
369
|
refresh_token,
|
|
324
|
-
...scope
|
|
370
|
+
...scope && { scope } || {},
|
|
325
371
|
}
|
|
326
372
|
}), { headers: REQUEST_HEADER }).pipe(catchError(() => {
|
|
327
373
|
this.logout();
|
|
@@ -336,24 +382,24 @@ class OAuthService {
|
|
|
336
382
|
}
|
|
337
383
|
}
|
|
338
384
|
getCleanedUnSearchParameters() {
|
|
339
|
-
const { search } = this.
|
|
385
|
+
const { search } = this.location;
|
|
340
386
|
let searchString = search.substr(1);
|
|
341
387
|
const hashKeys = ['code', 'state', 'error', 'error_description', 'session_state'];
|
|
342
388
|
hashKeys.forEach((hashKey) => {
|
|
343
389
|
const re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?');
|
|
344
390
|
searchString = searchString.replace(re, '');
|
|
345
391
|
});
|
|
346
|
-
return searchString.length
|
|
392
|
+
return searchString.length && `?${searchString}` || '';
|
|
347
393
|
}
|
|
348
394
|
cleanLocationHash() {
|
|
349
|
-
const { hash } = this.
|
|
395
|
+
const { hash } = this.location;
|
|
350
396
|
let curHash = hash.substr(1);
|
|
351
|
-
const hashKeys = ['access_token', 'token_type', 'expires_in', 'scope', 'state', 'error', 'error_description', 'session_state'];
|
|
397
|
+
const hashKeys = ['access_token', 'token_type', 'expires_in', 'scope', 'state', 'error', 'error_description', 'session_state', 'nonce'];
|
|
352
398
|
hashKeys.forEach((hashKey) => {
|
|
353
399
|
const re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?');
|
|
354
400
|
curHash = curHash.replace(re, '');
|
|
355
401
|
});
|
|
356
|
-
this.
|
|
402
|
+
this.location.hash = curHash;
|
|
357
403
|
}
|
|
358
404
|
emitState(parameters) {
|
|
359
405
|
const { state } = parameters;
|
|
@@ -362,7 +408,7 @@ class OAuthService {
|
|
|
362
408
|
}
|
|
363
409
|
}
|
|
364
410
|
}
|
|
365
|
-
OAuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthService, deps: [{ token: i1.HttpClient }, { token: i0.NgZone }, { token: OAUTH_CONFIG }, { token: LOCATION }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
411
|
+
OAuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthService, deps: [{ token: i1.HttpClient }, { token: i0.NgZone }, { token: OAUTH_CONFIG }, { token: LOCATION }, { token: i2.Location }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
366
412
|
OAuthService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthService });
|
|
367
413
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthService, decorators: [{
|
|
368
414
|
type: Injectable
|
|
@@ -372,7 +418,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImpor
|
|
|
372
418
|
}] }, { type: Location, decorators: [{
|
|
373
419
|
type: Inject,
|
|
374
420
|
args: [LOCATION]
|
|
375
|
-
}] }]; } });
|
|
421
|
+
}] }, { type: i2.Location }]; } });
|
|
376
422
|
|
|
377
423
|
class OAuthInterceptor {
|
|
378
424
|
constructor(oauthService) {
|
|
@@ -381,7 +427,7 @@ class OAuthInterceptor {
|
|
|
381
427
|
intercept(req, next) {
|
|
382
428
|
if (this.oauthService) {
|
|
383
429
|
if (!this.isPathExcepted(req)) {
|
|
384
|
-
const token = this.oauthService
|
|
430
|
+
const { token } = this.oauthService;
|
|
385
431
|
if (token && token.access_token) {
|
|
386
432
|
req = req.clone({
|
|
387
433
|
setHeaders: {
|
|
@@ -404,13 +450,16 @@ class OAuthInterceptor {
|
|
|
404
450
|
}
|
|
405
451
|
}
|
|
406
452
|
isPathExcepted(req) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
453
|
+
const { ignorePaths } = this.oauthService;
|
|
454
|
+
if (ignorePaths) {
|
|
455
|
+
for (const ignorePath of this.oauthService.ignorePaths) {
|
|
456
|
+
try {
|
|
457
|
+
if (req.url.match(ignorePath)) {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
411
462
|
}
|
|
412
|
-
}
|
|
413
|
-
catch (err) {
|
|
414
463
|
}
|
|
415
464
|
}
|
|
416
465
|
return false;
|
|
@@ -423,8 +472,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImpor
|
|
|
423
472
|
}], ctorParameters: function () { return [{ type: OAuthService }]; } });
|
|
424
473
|
|
|
425
474
|
class OAuthLoginComponent {
|
|
426
|
-
constructor(oauthService, location) {
|
|
475
|
+
constructor(oauthService, locationService, location) {
|
|
427
476
|
this.oauthService = oauthService;
|
|
477
|
+
this.locationService = locationService;
|
|
428
478
|
this.location = location;
|
|
429
479
|
this.subscription = new Subscription();
|
|
430
480
|
this._i18n = {
|
|
@@ -443,14 +493,15 @@ class OAuthLoginComponent {
|
|
|
443
493
|
this.OAuthType = OAuthType;
|
|
444
494
|
this.collapse = false;
|
|
445
495
|
this.type = this.oauthService.type;
|
|
446
|
-
this.redirectUri = this.location.href;
|
|
447
496
|
this.state$ = this.oauthService.state$.pipe(tap(s => this.stateChange.emit(s)));
|
|
448
497
|
this.status$ = this.oauthService.status$.pipe(tap(s => {
|
|
449
498
|
if (s === OAuthStatus.AUTHORIZED && this.profileName$) {
|
|
450
|
-
this.subscription.add(this.profileName$.subscribe(n => this.profileName = n));
|
|
499
|
+
this.subscription.add(this.profileName$.pipe(take(1)).subscribe(n => this.profileName = n));
|
|
451
500
|
}
|
|
452
501
|
else {
|
|
453
|
-
|
|
502
|
+
const { token } = this.oauthService;
|
|
503
|
+
const userInfo = token && token.id_token && JSON.parse(atob(token.id_token.split('.')[1])) || {};
|
|
504
|
+
this.profileName = userInfo.name || userInfo.username || userInfo.email || userInfo.sub || '';
|
|
454
505
|
}
|
|
455
506
|
}));
|
|
456
507
|
this.loginFunction = (p) => this.login(p);
|
|
@@ -465,14 +516,22 @@ class OAuthLoginComponent {
|
|
|
465
516
|
...i18n
|
|
466
517
|
};
|
|
467
518
|
}
|
|
519
|
+
set redirectUri(redirectUri) {
|
|
520
|
+
if (redirectUri) {
|
|
521
|
+
this._redirectUri = redirectUri;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
get redirectUri() {
|
|
525
|
+
return this._redirectUri || `${this.location.origin}${this.locationService.path(true) || '/'}`;
|
|
526
|
+
}
|
|
468
527
|
ngOnDestroy() {
|
|
469
528
|
this.subscription.unsubscribe();
|
|
470
529
|
}
|
|
471
530
|
logout() {
|
|
472
531
|
this.oauthService.logout();
|
|
473
532
|
}
|
|
474
|
-
login(parameters) {
|
|
475
|
-
this.oauthService.login(parameters);
|
|
533
|
+
async login(parameters) {
|
|
534
|
+
await this.oauthService.login(parameters);
|
|
476
535
|
this.collapse = false;
|
|
477
536
|
}
|
|
478
537
|
toggleCollapse() {
|
|
@@ -482,16 +541,18 @@ class OAuthLoginComponent {
|
|
|
482
541
|
this.collapse = false;
|
|
483
542
|
}
|
|
484
543
|
}
|
|
485
|
-
OAuthLoginComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthLoginComponent, deps: [{ token: OAuthService }, { token: LOCATION }], target: i0.ɵɵFactoryTarget.Component });
|
|
486
|
-
OAuthLoginComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: OAuthLoginComponent, selector: "oauth-login", inputs: { i18n: "i18n", 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: "<ng-container *ngIf=\"state$ | async\"></ng-container>\r\n<ng-container *ngIf=\"loginTemplate; else defaultLogin\"\r\n [ngTemplateOutlet]=\"loginTemplate\"\r\n [ngTemplateOutletContext]=\"{login: loginFunction, logout: logoutFunction, status: status$ | async}\">\r\n</ng-container>\r\n<ng-template #defaultLogin>\r\n <ng-container *ngIf=\"status$ | async as status\">\r\n <ng-container *ngIf=\"type === OAuthType.RESOURCE; else noResource\">\r\n <div class=\"oauth dropdown text-
|
|
544
|
+
OAuthLoginComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthLoginComponent, deps: [{ token: OAuthService }, { token: i2.Location }, { token: LOCATION }], target: i0.ɵɵFactoryTarget.Component });
|
|
545
|
+
OAuthLoginComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: OAuthLoginComponent, selector: "oauth-login", inputs: { i18n: "i18n", redirectUri: "redirectUri", 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: "<ng-container *ngIf=\"state$ | async\"></ng-container>\r\n<ng-container *ngIf=\"loginTemplate; else defaultLogin\"\r\n [ngTemplateOutlet]=\"loginTemplate\"\r\n [ngTemplateOutletContext]=\"{login: loginFunction, logout: logoutFunction, status: status$ | async}\">\r\n</ng-container>\r\n<ng-template #defaultLogin>\r\n <ng-container *ngIf=\"status$ | async as status\">\r\n <ng-container *ngIf=\"type === OAuthType.RESOURCE; else noResource\">\r\n <div class=\"oauth dropdown text-end p-3 {{collapse ? 'show': ''}}\">\r\n <button class=\"btn btn-link p-0 dropdown-toggle\"\r\n (click)=\"status === OAuthStatus.AUTHORIZED ? logout() : toggleCollapse()\">\r\n <ng-container *ngTemplateOutlet=\"message\"></ng-container>\r\n </button>\r\n <div class=\"dropdown-menu mr-3 {{collapse ? 'show': ''}}\">\r\n <form class=\"p-3\" #form=\"ngForm\"\r\n *ngIf=\"status === OAuthStatus.NOT_AUTHORIZED || status === OAuthStatus.DENIED\"\r\n (submit)=\"login({username: username, password: password})\">\r\n <div class=\"mb-3\">\r\n <input type=\"text\"\r\n class=\"form-control\"\r\n name=\"username\"\r\n required\r\n [(ngModel)]=\"username\"\r\n [placeholder]=\"i18n.username\">\r\n </div>\r\n <div class=\"mb-3\">\r\n <input type=\"password\"\r\n class=\"form-control\"\r\n name=\"password\"\r\n required\r\n [(ngModel)]=\"password\"\r\n [placeholder]=\"i18n.password\">\r\n </div>\r\n <div class=\"text-end\">\r\n <button type=\"submit\"\r\n class=\"btn btn-primary\"\r\n [disabled]=\"form.invalid\">{{i18n.submit}}</button>\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #noResource>\r\n <a role=\"button\" class=\"oauth\"\r\n (click)=\"status === OAuthStatus.AUTHORIZED ? logout() : login({redirectUri: redirectUri, state:state})\">\r\n <ng-container *ngTemplateOutlet=\"message\"></ng-container>\r\n </a>\r\n </ng-template>\r\n\r\n <ng-template #message>\r\n <span *ngIf=\"status === OAuthStatus.NOT_AUTHORIZED\">{{i18n.notAuthorized}}</span>\r\n <span *ngIf=\"status === OAuthStatus.AUTHORIZED\">\r\n {{i18n.authorized}}<strong> {{profileName}}</strong>\r\n </span>\r\n <span *ngIf=\"status === OAuthStatus.DENIED\">{{i18n.denied}}</span>\r\n </ng-template>\r\n </ng-container>\r\n</ng-template>\r\n\r\n", 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"], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], pipes: { "async": i2.AsyncPipe }, encapsulation: i0.ViewEncapsulation.None });
|
|
487
546
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: OAuthLoginComponent, decorators: [{
|
|
488
547
|
type: Component,
|
|
489
|
-
args: [{ selector: 'oauth-login', template: "<ng-container *ngIf=\"state$ | async\"></ng-container>\r\n<ng-container *ngIf=\"loginTemplate; else defaultLogin\"\r\n [ngTemplateOutlet]=\"loginTemplate\"\r\n [ngTemplateOutletContext]=\"{login: loginFunction, logout: logoutFunction, status: status$ | async}\">\r\n</ng-container>\r\n<ng-template #defaultLogin>\r\n <ng-container *ngIf=\"status$ | async as status\">\r\n <ng-container *ngIf=\"type === OAuthType.RESOURCE; else noResource\">\r\n <div class=\"oauth dropdown text-
|
|
490
|
-
}], ctorParameters: function () { return [{ type: OAuthService }, { type: Location, decorators: [{
|
|
548
|
+
args: [{ selector: 'oauth-login', encapsulation: ViewEncapsulation.None, template: "<ng-container *ngIf=\"state$ | async\"></ng-container>\r\n<ng-container *ngIf=\"loginTemplate; else defaultLogin\"\r\n [ngTemplateOutlet]=\"loginTemplate\"\r\n [ngTemplateOutletContext]=\"{login: loginFunction, logout: logoutFunction, status: status$ | async}\">\r\n</ng-container>\r\n<ng-template #defaultLogin>\r\n <ng-container *ngIf=\"status$ | async as status\">\r\n <ng-container *ngIf=\"type === OAuthType.RESOURCE; else noResource\">\r\n <div class=\"oauth dropdown text-end p-3 {{collapse ? 'show': ''}}\">\r\n <button class=\"btn btn-link p-0 dropdown-toggle\"\r\n (click)=\"status === OAuthStatus.AUTHORIZED ? logout() : toggleCollapse()\">\r\n <ng-container *ngTemplateOutlet=\"message\"></ng-container>\r\n </button>\r\n <div class=\"dropdown-menu mr-3 {{collapse ? 'show': ''}}\">\r\n <form class=\"p-3\" #form=\"ngForm\"\r\n *ngIf=\"status === OAuthStatus.NOT_AUTHORIZED || status === OAuthStatus.DENIED\"\r\n (submit)=\"login({username: username, password: password})\">\r\n <div class=\"mb-3\">\r\n <input type=\"text\"\r\n class=\"form-control\"\r\n name=\"username\"\r\n required\r\n [(ngModel)]=\"username\"\r\n [placeholder]=\"i18n.username\">\r\n </div>\r\n <div class=\"mb-3\">\r\n <input type=\"password\"\r\n class=\"form-control\"\r\n name=\"password\"\r\n required\r\n [(ngModel)]=\"password\"\r\n [placeholder]=\"i18n.password\">\r\n </div>\r\n <div class=\"text-end\">\r\n <button type=\"submit\"\r\n class=\"btn btn-primary\"\r\n [disabled]=\"form.invalid\">{{i18n.submit}}</button>\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #noResource>\r\n <a role=\"button\" class=\"oauth\"\r\n (click)=\"status === OAuthStatus.AUTHORIZED ? logout() : login({redirectUri: redirectUri, state:state})\">\r\n <ng-container *ngTemplateOutlet=\"message\"></ng-container>\r\n </a>\r\n </ng-template>\r\n\r\n <ng-template #message>\r\n <span *ngIf=\"status === OAuthStatus.NOT_AUTHORIZED\">{{i18n.notAuthorized}}</span>\r\n <span *ngIf=\"status === OAuthStatus.AUTHORIZED\">\r\n {{i18n.authorized}}<strong> {{profileName}}</strong>\r\n </span>\r\n <span *ngIf=\"status === OAuthStatus.DENIED\">{{i18n.denied}}</span>\r\n </ng-template>\r\n </ng-container>\r\n</ng-template>\r\n\r\n", 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"] }]
|
|
549
|
+
}], ctorParameters: function () { return [{ type: OAuthService }, { type: i2.Location }, { type: Location, decorators: [{
|
|
491
550
|
type: Inject,
|
|
492
551
|
args: [LOCATION]
|
|
493
552
|
}] }]; }, propDecorators: { i18n: [{
|
|
494
553
|
type: Input
|
|
554
|
+
}], redirectUri: [{
|
|
555
|
+
type: Input
|
|
495
556
|
}], state: [{
|
|
496
557
|
type: Input
|
|
497
558
|
}], stateChange: [{
|
|
@@ -562,8 +623,7 @@ const defaultConfig = (storage) => {
|
|
|
562
623
|
return {
|
|
563
624
|
storage,
|
|
564
625
|
storageKey: 'token',
|
|
565
|
-
ignorePaths: []
|
|
566
|
-
pkce: false
|
|
626
|
+
ignorePaths: []
|
|
567
627
|
};
|
|
568
628
|
};
|
|
569
629
|
class OAuthModule {
|
|
@@ -635,4 +695,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImpor
|
|
|
635
695
|
* Generated bundle index. Do not edit.
|
|
636
696
|
*/
|
|
637
697
|
|
|
638
|
-
export { LOCATION, OAUTH_CONFIG, OAuthInterceptor, OAuthLoginComponent, OAuthModule, OAuthService, OAuthStatus, OAuthType, SERVER_HOST, SERVER_PATH, STORAGE };
|
|
698
|
+
export { LOCATION, OAUTH_CONFIG, OAUTH_TOKEN, OAuthInterceptor, OAuthLoginComponent, OAuthModule, OAuthService, OAuthStatus, OAuthType, SERVER_HOST, SERVER_PATH, STORAGE };
|