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.
@@ -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 { catchError, concatMap, delay, switchMap, tap } from 'rxjs/operators';
6
- import { ReplaySubject, EMPTY, from, of, noop, throwError, Subscription } from 'rxjs';
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.init();
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.locationService;
73
- const isImplicitRedirect = hash && /(#access_token=)|(#error=)/.test(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
- if (isImplicitRedirect) {
79
- const parameters = parseOauthUri(hash.substr(1));
80
- this.emitState(parameters);
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
- else if (isAuthCodeRedirect) {
92
- const parameters = parseOauthUri(search.substr(1));
93
- this.emitState(parameters);
94
- const newParametersString = this.getCleanedUnSearchParameters();
95
- if (parameters && parameters.code) {
96
- const { clientId, clientSecret, tokenPath, scope } = this.authConfig.config;
97
- const codeVerifier = savedToken && savedToken.codeVerifier;
98
- setTimeout(() => {
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.code,
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 = { error: 'error' };
139
+ }), { headers: REQUEST_HEADER }).pipe(catchError((err) => {
140
+ this.token = err;
111
141
  this.status = OAuthStatus.DENIED;
112
- this.locationService.href = `${origin}${pathname}${newParametersString}`;
142
+ this.locationService.replaceState(`${pathname}${newParametersString}`);
113
143
  return EMPTY;
114
144
  })).subscribe(token => {
115
145
  this.token = token;
116
- // authorized event will be triggered after redirect
117
- this.locationService.href = `${origin}${pathname}${newParametersString}`;
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 (access_token) {
133
- this.token = savedToken;
134
- if (refresh_token) {
135
- setTimeout(() => {
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.AUTHORIZED;
163
+ this.status = error && OAuthStatus.DENIED || OAuthStatus.NOT_AUTHORIZED;
141
164
  }
142
165
  }
143
- }
144
- else {
145
- this.status = OAuthStatus.NOT_AUTHORIZED;
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).then();
176
+ await this.authorizationCodeLogin(parameters);
154
177
  }
155
178
  else if (this.isImplicitType(parameters)) {
156
- this.implicitLogin(parameters).then();
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 ? { scope } : {},
245
+ ...scope && { scope } || {},
223
246
  username,
224
247
  password
225
248
  }
226
- }), { headers: REQUEST_HEADER }).pipe(catchError(() => {
227
- this.token = null;
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.locationService.replace(authUrl);
260
+ this.location.replace(authUrl);
238
261
  }
239
262
  async implicitLogin(parameters) {
240
263
  const authUrl = await this.toAuthorizationUrl(parameters, OAuthType.IMPLICIT);
241
- this.locationService.replace(authUrl);
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
- const appendChar = config.authorizePath.includes('?') ? '&' : '?';
276
- const clientId = `${appendChar}client_id=${config.clientId}`;
277
- const redirectUri = `&redirect_uri=${encodeURIComponent(parameters.redirectUri)}`;
278
- const responseTypeString = `&response_type=${responseType}`;
279
- const scope = `&scope=${encodeURIComponent(config.scope || '')}`;
280
- const state = `&state=${encodeURIComponent(parameters.state || '')}`;
281
- const codeVerifier = config.pkce && randomString() || null;
282
- if (codeVerifier) {
283
- this.token = { codeVerifier }; //save the code verifier before we redirect
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
- const codeChallenge = codeVerifier ? `&code_challenge=${await pkce(codeVerifier)}&code_challenge_method=S256` : '';
286
- const parametersString = `${clientId}${redirectUri}${responseTypeString}${scope}${state}${codeChallenge}`;
287
- return `${config.authorizePath}${parametersString}`;
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 ? { client_secret: clientSecret } : {},
367
+ ...clientSecret && { client_secret: clientSecret } || {},
322
368
  grant_type: 'refresh_token',
323
369
  refresh_token,
324
- ...scope ? { 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.locationService;
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 ? `?${searchString}` : '';
392
+ return searchString.length && `?${searchString}` || '';
347
393
  }
348
394
  cleanLocationHash() {
349
- const { hash } = this.locationService;
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.locationService.hash = curHash;
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.token;
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
- for (const ignorePath of this.oauthService.ignorePaths) {
408
- try {
409
- if (req.url.match(ignorePath)) {
410
- return true;
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
- this.profileName = '';
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-right 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=\"form-group\">\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=\"form-group\">\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-right\">\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>&nbsp;{{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 } });
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>&nbsp;{{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-right 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=\"form-group\">\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=\"form-group\">\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-right\">\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>&nbsp;{{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"] }]
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>&nbsp;{{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 };