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:
parent
f868f5df91
commit
63a5750f28
5 changed files with 14 additions and 25 deletions
|
|
@ -3,20 +3,22 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c
|
||||||
import {Observable, switchMap} from 'rxjs';
|
import {Observable, switchMap} from 'rxjs';
|
||||||
import { AccountService } from '../_services/account.service';
|
import { AccountService } from '../_services/account.service';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
|
import { OidcService } from '../_services/oidc.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtInterceptor implements HttpInterceptor {
|
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>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
return this.accountService.currentUser$.pipe(
|
return this.accountService.currentUser$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap(user => {
|
switchMap(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
const token = this.oidcService.hasValidToken() ? this.oidcService.token : user.token;
|
||||||
request = request.clone({
|
request = request.clone({
|
||||||
setHeaders: {
|
setHeaders: {
|
||||||
Authorization: `Bearer ${user.oidcToken ?? user.token}`
|
Authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ import {Preferences} from './preferences/preferences';
|
||||||
// This interface is only used for login and storing/retrieving JWT from local storage
|
// This interface is only used for login and storing/retrieving JWT from local storage
|
||||||
export interface User {
|
export interface User {
|
||||||
username: string;
|
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;
|
token: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
|
||||||
import {Action} from "./action-factory.service";
|
import {Action} from "./action-factory.service";
|
||||||
import {LicenseService} from "./license.service";
|
import {LicenseService} from "./license.service";
|
||||||
import {LocalizationService} from "./localization.service";
|
import {LocalizationService} from "./localization.service";
|
||||||
|
import {OidcService} from "./oidc.service";
|
||||||
|
|
||||||
export enum Role {
|
export enum Role {
|
||||||
Admin = 'Admin',
|
Admin = 'Admin',
|
||||||
|
|
@ -46,6 +47,7 @@ export const allRoles = [
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
private readonly oidcService = inject(OidcService);
|
||||||
private readonly licenseService = inject(LicenseService);
|
private readonly licenseService = inject(LicenseService);
|
||||||
private readonly localizationService = inject(LocalizationService);
|
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) {
|
canInvokeAction(user: User, action: Action) {
|
||||||
const isAdmin = this.hasAdminRole(user);
|
const isAdmin = this.hasAdminRole(user);
|
||||||
const canDownload = this.hasDownloadRole(user);
|
const canDownload = this.hasDownloadRole(user);
|
||||||
|
|
@ -219,7 +217,6 @@ export class AccountService {
|
||||||
tap((response: User) => {
|
tap((response: User) => {
|
||||||
const user = response;
|
const user = response;
|
||||||
if (user) {
|
if (user) {
|
||||||
user.oidcToken = token;
|
|
||||||
this.setCurrentUser(user);
|
this.setCurrentUser(user);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -263,7 +260,7 @@ export class AccountService {
|
||||||
this.licenseService.hasValidLicense().subscribe();
|
this.licenseService.hasValidLicense().subscribe();
|
||||||
}
|
}
|
||||||
// oidc handles refreshing itself
|
// oidc handles refreshing itself
|
||||||
if (!this.currentUser.oidcToken) {
|
if (!this.oidcService.hasValidToken()) {
|
||||||
this.startRefreshTokenTimer();
|
this.startRefreshTokenTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {DashboardUpdateEvent} from "../_models/events/dashboard-update-event";
|
||||||
import {SideNavUpdateEvent} from "../_models/events/sidenav-update-event";
|
import {SideNavUpdateEvent} from "../_models/events/sidenav-update-event";
|
||||||
import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event";
|
import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event";
|
||||||
import {ExternalMatchRateLimitErrorEvent} from "../_models/events/external-match-rate-limit-error-event";
|
import {ExternalMatchRateLimitErrorEvent} from "../_models/events/external-match-rate-limit-error-event";
|
||||||
|
import {OidcService} from "./oidc.service";
|
||||||
|
|
||||||
export enum EVENTS {
|
export enum EVENTS {
|
||||||
UpdateAvailable = 'UpdateAvailable',
|
UpdateAvailable = 'UpdateAvailable',
|
||||||
|
|
@ -146,7 +147,7 @@ export class MessageHubService {
|
||||||
*/
|
*/
|
||||||
public onlineUsers$ = this.onlineUsersSource.asObservable();
|
public onlineUsers$ = this.onlineUsersSource.asObservable();
|
||||||
|
|
||||||
constructor() {}
|
constructor(private oidcService: OidcService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that an event is of the type passed
|
* Tests that an event is of the type passed
|
||||||
|
|
@ -165,7 +166,7 @@ export class MessageHubService {
|
||||||
createHubConnection(user: User) {
|
createHubConnection(user: User) {
|
||||||
this.hubConnection = new HubConnectionBuilder()
|
this.hubConnection = new HubConnectionBuilder()
|
||||||
.withUrl(this.hubUrl + 'messages', {
|
.withUrl(this.hubUrl + 'messages', {
|
||||||
accessTokenFactory: () => user.oidcToken ?? user.token
|
accessTokenFactory: () => this.oidcService.hasValidToken() ? this.oidcService.token : user.token
|
||||||
})
|
})
|
||||||
.withAutomaticReconnect()
|
.withAutomaticReconnect()
|
||||||
//.withStatefulReconnect() // Requires signalr@8.0
|
//.withStatefulReconnect() // Requires signalr@8.0
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ export class OidcService {
|
||||||
|
|
||||||
private readonly oauth2 = inject(OAuthService);
|
private readonly oauth2 = inject(OAuthService);
|
||||||
private readonly httpClient = inject(HttpClient);
|
private readonly httpClient = inject(HttpClient);
|
||||||
private readonly accountService = inject(AccountService);
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly toastR = inject(ToastrService);
|
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 => {
|
this.config().subscribe(oidcSetting => {
|
||||||
if (!oidcSetting.authority) {
|
if (!oidcSetting.authority) {
|
||||||
this._loaded.set(true);
|
this._loaded.set(true);
|
||||||
|
|
@ -121,4 +109,8 @@ export class OidcService {
|
||||||
return this.oauth2.getAccessToken();
|
return this.oauth2.getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasValidToken() {
|
||||||
|
return this.oauth2.hasValidAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue