Only use OIDC token when it's valid

Need to extend the wait logic to the JWT interceptor as well
This commit is contained in:
Amelia 2025-07-03 15:58:47 +02:00
parent f868f5df91
commit 63a5750f28
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
5 changed files with 14 additions and 25 deletions

View file

@ -3,20 +3,22 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c
import {Observable, switchMap} from 'rxjs';
import { AccountService } from '../_services/account.service';
import { take } from 'rxjs/operators';
import { OidcService } from '../_services/oidc.service';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {}
constructor(private accountService: AccountService, private oidcService: OidcService) { }
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return this.accountService.currentUser$.pipe(
take(1),
switchMap(user => {
if (user) {
const token = this.oidcService.hasValidToken() ? this.oidcService.token : user.token;
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.oidcToken ?? user.token}`
Authorization: `Bearer ${token}`
}
});
}

View file

@ -4,9 +4,6 @@ import {Preferences} from './preferences/preferences';
// This interface is only used for login and storing/retrieving JWT from local storage
export interface User {
username: string;
// This is set by the oidc service, will always take precedence over the Kavita generated token
// When set, the refresh logic for the Kavita token will not run
oidcToken: string;
token: string;
refreshToken: string;
roles: string[];

View file

@ -17,6 +17,7 @@ import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
import {Action} from "./action-factory.service";
import {LicenseService} from "./license.service";
import {LocalizationService} from "./localization.service";
import {OidcService} from "./oidc.service";
export enum Role {
Admin = 'Admin',
@ -46,6 +47,7 @@ export const allRoles = [
export class AccountService {
private readonly destroyRef = inject(DestroyRef);
private readonly oidcService = inject(OidcService);
private readonly licenseService = inject(LicenseService);
private readonly localizationService = inject(LocalizationService);
@ -92,10 +94,6 @@ export class AccountService {
});
}
oidcEnabled() {
return this.httpClient.get<boolean>(this.baseUrl + "oidc/enabled");
}
canInvokeAction(user: User, action: Action) {
const isAdmin = this.hasAdminRole(user);
const canDownload = this.hasDownloadRole(user);
@ -219,7 +217,6 @@ export class AccountService {
tap((response: User) => {
const user = response;
if (user) {
user.oidcToken = token;
this.setCurrentUser(user);
}
}),
@ -263,7 +260,7 @@ export class AccountService {
this.licenseService.hasValidLicense().subscribe();
}
// oidc handles refreshing itself
if (!this.currentUser.oidcToken) {
if (!this.oidcService.hasValidToken()) {
this.startRefreshTokenTimer();
}
}

View file

@ -11,6 +11,7 @@ import {DashboardUpdateEvent} from "../_models/events/dashboard-update-event";
import {SideNavUpdateEvent} from "../_models/events/sidenav-update-event";
import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event";
import {ExternalMatchRateLimitErrorEvent} from "../_models/events/external-match-rate-limit-error-event";
import {OidcService} from "./oidc.service";
export enum EVENTS {
UpdateAvailable = 'UpdateAvailable',
@ -146,7 +147,7 @@ export class MessageHubService {
*/
public onlineUsers$ = this.onlineUsersSource.asObservable();
constructor() {}
constructor(private oidcService: OidcService) {}
/**
* Tests that an event is of the type passed
@ -165,7 +166,7 @@ export class MessageHubService {
createHubConnection(user: User) {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.hubUrl + 'messages', {
accessTokenFactory: () => user.oidcToken ?? user.token
accessTokenFactory: () => this.oidcService.hasValidToken() ? this.oidcService.token : user.token
})
.withAutomaticReconnect()
//.withStatefulReconnect() // Requires signalr@8.0

View file

@ -18,7 +18,6 @@ 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);
@ -56,17 +55,6 @@ 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 => {
if (!oidcSetting.authority) {
this._loaded.set(true);
@ -121,4 +109,8 @@ export class OidcService {
return this.oauth2.getAccessToken();
}
hasValidToken() {
return this.oauth2.hasValidAccessToken();
}
}