Bits of cleanup & support custom base urls
This commit is contained in:
parent
08914f7546
commit
9979220641
3 changed files with 52 additions and 26 deletions
|
@ -1,16 +1,26 @@
|
||||||
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
|
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
|
||||||
import {Injectable} from "@angular/core";
|
import {inject, Injectable} from "@angular/core";
|
||||||
import {Observable, take} from "rxjs";
|
import {catchError, filter, Observable, of, take, timeout} from "rxjs";
|
||||||
import {OidcService} from "../_services/oidc.service";
|
import {OidcService} from "../_services/oidc.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class OidcResolver implements Resolve<any> {
|
export class OidcResolver implements Resolve<any> {
|
||||||
|
|
||||||
constructor(private oidcService: OidcService) {}
|
private oidcService = inject(OidcService);
|
||||||
|
private toastR = inject(ToastrService);
|
||||||
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
|
||||||
return this.oidcService.loaded$.pipe(take(1));
|
return this.oidcService.loaded$.pipe(
|
||||||
|
filter(value => value),
|
||||||
|
take(1),
|
||||||
|
timeout(5000),
|
||||||
|
catchError(err => {
|
||||||
|
console.log(err);
|
||||||
|
this.toastR.error("oidc.timeout");
|
||||||
|
return of(true);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import {HttpClient} from "@angular/common/http";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {OidcPublicConfig} from "../admin/_models/oidc-config";
|
import {OidcPublicConfig} from "../admin/_models/oidc-config";
|
||||||
import {AccountService} from "./account.service";
|
import {AccountService} from "./account.service";
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed, toObservable} from "@angular/core/rxjs-interop";
|
||||||
import {take} from "rxjs/operators";
|
import {take} from "rxjs/operators";
|
||||||
import {ToastrService} from "ngx-toastr";
|
import {ToastrService} from "ngx-toastr";
|
||||||
import {translate} from "@jsverse/transloco";
|
import {translate} from "@jsverse/transloco";
|
||||||
|
import {APP_BASE_HREF} from "@angular/common";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -21,19 +22,32 @@ export class OidcService {
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly toastR = inject(ToastrService);
|
private readonly toastR = inject(ToastrService);
|
||||||
|
|
||||||
baseUrl = environment.apiUrl;
|
protected readonly baseUrl = inject(APP_BASE_HREF);
|
||||||
|
apiBaseUrl = environment.apiUrl;
|
||||||
|
|
||||||
private readonly loaded = new BehaviorSubject<boolean>(false);
|
/**
|
||||||
public readonly loaded$: Observable<boolean> = this.loaded.asObservable();
|
* True when the OIDC discovery document has been loaded, and login tried. Or no OIDC has been set up
|
||||||
|
*/
|
||||||
|
private readonly _loaded = signal(false);
|
||||||
|
public readonly loaded = this._loaded.asReadonly();
|
||||||
|
public readonly loaded$ = toObservable(this.loaded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OIDC discovery document has been loaded, and login tried and OIDC has been set up
|
||||||
|
*/
|
||||||
private readonly _ready = signal(false);
|
private readonly _ready = signal(false);
|
||||||
public readonly ready = this._ready.asReadonly();
|
public readonly ready = this._ready.asReadonly();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public OIDC settings
|
||||||
|
*/
|
||||||
private readonly _settings = signal<OidcPublicConfig | undefined>(undefined);
|
private readonly _settings = signal<OidcPublicConfig | undefined>(undefined);
|
||||||
public readonly settings = this._settings.asReadonly();
|
public readonly settings = this._settings.asReadonly();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// log events in dev
|
// log events in dev
|
||||||
if (!environment.production) {
|
if (!environment.production) {
|
||||||
this.oauth2.events.subscribe(event => {
|
this.oauth2.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(event => {
|
||||||
if (event instanceof OAuthErrorEvent) {
|
if (event instanceof OAuthErrorEvent) {
|
||||||
console.error('OAuthErrorEvent Object:', event);
|
console.error('OAuthErrorEvent Object:', event);
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,9 +56,20 @@ export class OidcService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.oauth2.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
||||||
|
if (event.type !== "token_refreshed" && event.type != 'token_received') return;
|
||||||
|
|
||||||
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||||
|
if (!user) return; // Don't update tokens when we're not logged in. But what's going on?
|
||||||
|
|
||||||
|
// TODO: Do we need to refresh the SignalR connection here?
|
||||||
|
user.oidcToken = this.token;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.config().subscribe(oidcSetting => {
|
this.config().subscribe(oidcSetting => {
|
||||||
if (!oidcSetting.authority) {
|
if (!oidcSetting.authority) {
|
||||||
this.loaded.next(true);
|
this._loaded.set(true);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,30 +78,20 @@ export class OidcService {
|
||||||
clientId: oidcSetting.clientId,
|
clientId: oidcSetting.clientId,
|
||||||
// Require https in production unless localhost
|
// Require https in production unless localhost
|
||||||
requireHttps: environment.production ? 'remoteOnly' : false,
|
requireHttps: environment.production ? 'remoteOnly' : false,
|
||||||
redirectUri: window.location.origin + "/oidc/callback",
|
redirectUri: window.location.origin + this.baseUrl + "oidc/callback",
|
||||||
postLogoutRedirectUri: window.location.origin + "/login",
|
postLogoutRedirectUri: window.location.origin + this.baseUrl + "login",
|
||||||
showDebugInformation: !environment.production,
|
showDebugInformation: !environment.production,
|
||||||
responseType: 'code',
|
responseType: 'code',
|
||||||
scope: "openid profile email roles offline_access",
|
scope: "openid profile email roles offline_access",
|
||||||
|
// Not all OIDC providers follow this nicely
|
||||||
strictDiscoveryDocumentValidation: false,
|
strictDiscoveryDocumentValidation: false,
|
||||||
});
|
});
|
||||||
this._settings.set(oidcSetting);
|
this._settings.set(oidcSetting);
|
||||||
this.oauth2.setupAutomaticSilentRefresh();
|
this.oauth2.setupAutomaticSilentRefresh();
|
||||||
|
|
||||||
this.oauth2.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
||||||
if (event.type !== "token_refreshed") return;
|
|
||||||
|
|
||||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
|
||||||
if (!user) return; // Don't update tokens when we're not logged in. But what's going on?
|
|
||||||
|
|
||||||
// TODO: Do we need to refresh the SignalR connection here?
|
|
||||||
user.oidcToken = this.token;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
from(this.oauth2.loadDiscoveryDocumentAndTryLogin()).subscribe({
|
from(this.oauth2.loadDiscoveryDocumentAndTryLogin()).subscribe({
|
||||||
next: _ => {
|
next: _ => {
|
||||||
this.loaded.next(true);
|
this._loaded.set(true);
|
||||||
this._ready.set(true);
|
this._ready.set(true);
|
||||||
},
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
|
@ -99,7 +114,7 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
|
|
||||||
config() {
|
config() {
|
||||||
return this.httpClient.get<OidcPublicConfig>(this.baseUrl + "oidc/config");
|
return this.httpClient.get<OidcPublicConfig>(this.apiBaseUrl + "oidc/config");
|
||||||
}
|
}
|
||||||
|
|
||||||
get token() {
|
get token() {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"title": "OpenID Connect Callback",
|
"title": "OpenID Connect Callback",
|
||||||
"login": "Back to login screen",
|
"login": "Back to login screen",
|
||||||
"error-loading-info": "An error occurred loading OpenID Connect info, contact your administrator",
|
"error-loading-info": "An error occurred loading OpenID Connect info, contact your administrator",
|
||||||
|
"timeout": "OIDC resolution has timed out or an error has occurred",
|
||||||
"settings": {
|
"settings": {
|
||||||
"save": "{{common.save}}",
|
"save": "{{common.save}}",
|
||||||
"notice": "Notice",
|
"notice": "Notice",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue