From e5c716d2343865fc1c0da9fbf0436f43bc748f09 Mon Sep 17 00:00:00 2001 From: Amelia <77553571+Fesaa@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:42:07 +0200 Subject: [PATCH] Tweak refresh logic for OIDC --- UI/Web/src/app/_services/oidc.service.ts | 39 ++++++++++++------------ UI/Web/src/app/app.component.ts | 17 ++++++----- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/UI/Web/src/app/_services/oidc.service.ts b/UI/Web/src/app/_services/oidc.service.ts index 4ebc1fc50..aac2ae9ce 100644 --- a/UI/Web/src/app/_services/oidc.service.ts +++ b/UI/Web/src/app/_services/oidc.service.ts @@ -4,13 +4,20 @@ import {from} from "rxjs"; import {HttpClient} from "@angular/common/http"; import {environment} from "../../environments/environment"; import {OidcPublicConfig} from "../admin/_models/oidc-config"; -import {AccountService} from "./account.service"; import {takeUntilDestroyed, toObservable} from "@angular/core/rxjs-interop"; -import {take} from "rxjs/operators"; import {ToastrService} from "ngx-toastr"; import {translate} from "@jsverse/transloco"; import {APP_BASE_HREF} from "@angular/common"; -import {MessageHubService} from "./message-hub.service"; + +/** + * Enum mirror of angular-oauth2-oidc events which are used in Kavita + */ +export enum OidcEvents { + /** + * Fired on token refresh, and when the first token is recieved + */ + TokenRefreshed = "token_refreshed" +} @Injectable({ providedIn: 'root' @@ -19,14 +26,14 @@ export class OidcService { private readonly oauth2 = inject(OAuthService); private readonly httpClient = inject(HttpClient); - private readonly accountService = inject(AccountService); private readonly destroyRef = inject(DestroyRef); private readonly toastR = inject(ToastrService); - private readonly messageHub = inject(MessageHubService); protected readonly baseUrl = inject(APP_BASE_HREF); apiBaseUrl = environment.apiUrl; + public events$ = this.oauth2.events; + /** * True when the OIDC discovery document has been loaded, and login tried. Or no OIDC has been set up */ @@ -47,29 +54,19 @@ export class OidcService { public readonly settings = this._settings.asReadonly(); constructor() { + this.oauth2.setStorage(localStorage); + // log events in dev if (!environment.production) { this.oauth2.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(event => { if (event instanceof OAuthErrorEvent) { - console.error('OAuthErrorEvent Object:', event); + console.error('OAuthErrorEvent:', event); } else { - console.debug('OAuthEvent Object:', event); + console.debug('OAuthEvent:', event); } }); } - 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? - - user.oidcToken = this.token; - this.messageHub.stopHubConnection(); - this.messageHub.createHubConnection(user); - }); - }); - this.getPublicOidcConfig().subscribe(oidcSetting => { this._settings.set(oidcSetting); @@ -96,6 +93,10 @@ export class OidcService { from(this.oauth2.loadDiscoveryDocumentAndTryLogin()).subscribe({ next: _ => { this._loaded.set(true); + + if (!this.oauth2.hasValidAccessToken() && this.oauth2.getRefreshToken()) { + this.oauth2.refreshToken().catch(err => console.error("failed to refresh token on startup", err)); + } }, error: error => { console.log(error); diff --git a/UI/Web/src/app/app.component.ts b/UI/Web/src/app/app.component.ts index 9f143f785..a4953392e 100644 --- a/UI/Web/src/app/app.component.ts +++ b/UI/Web/src/app/app.component.ts @@ -25,7 +25,7 @@ import {TranslocoService} from "@jsverse/transloco"; import {VersionService} from "./_services/version.service"; import {LicenseService} from "./_services/license.service"; import {LocalizationService} from "./_services/localization.service"; -import {OidcService} from "./_services/oidc.service"; +import {OidcEvents, OidcService} from "./_services/oidc.service"; @Component({ selector: 'app-root', @@ -52,7 +52,7 @@ export class AppComponent implements OnInit { private readonly document = inject(DOCUMENT); private readonly translocoService = inject(TranslocoService); private readonly versionService = inject(VersionService); // Needs to be injected to run background job - private readonly oidcService = inject(OidcService); // Needed to auto login + private readonly oidcService = inject(OidcService); private readonly licenseService = inject(LicenseService); private readonly localizationService = inject(LocalizationService); @@ -100,11 +100,15 @@ export class AppComponent implements OnInit { this.localizationService.getLocales().subscribe(); // This will cache the localizations on startup - // Login automatically when a token is available - effect(() => { - const inUse = this.oidcService.inUse(); + // Update token, or login when one becomes available + this.oidcService.events$.subscribe(event => { + if (event.type !== OidcEvents.TokenRefreshed) return; + const user = this.accountService.currentUserSignal(); - if (!inUse || !this.oidcService.token || user) return; + if (user) { + user.oidcToken = this.oidcService.token; + return; + } this.accountService.loginByToken(this.oidcService.token).subscribe({ next: () => { @@ -115,7 +119,6 @@ export class AppComponent implements OnInit { } }); }); - } @HostListener('window:resize', ['$event'])