token expiration ajustment for testing

This commit is contained in:
2026-04-08 11:44:59 -07:00
parent 6a995582f7
commit fbe9fa19c7
2 changed files with 26 additions and 7 deletions
+10 -1
View File
@@ -9,7 +9,16 @@ namespace WinStudentGoalTracker.Services;
public class TokenService public class TokenService
{ {
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly int _tokenExpiryInSeconds = 60 * 15; // 15 minutes
// Temporary lowered to 1 minute expiration to test front end auth management
// and see if we get any random logouts or if token refresh is working as intended.
private readonly int _tokenExpiryInSeconds = 60; // 1 minute
// This is for the temporary non program scoped token that is granted at login
// and is only used for the selection of a program.
// In theory we have a bug here if someone sits at the program selection screen for more
// Than 5 minutes, the program selection will fail and they will be logged out.
private readonly int _sessionTokenExpiryInSeconds = 60 * 5; // 5 minutes private readonly int _sessionTokenExpiryInSeconds = 60 * 5; // 5 minutes
public TokenService(IConfiguration config) public TokenService(IConfiguration config)
@@ -1,6 +1,6 @@
import { computed, inject, Injectable, signal } from '@angular/core'; import { computed, inject, Injectable, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { catchError, EMPTY, Observable, of, Subject, tap } from 'rxjs'; import { catchError, finalize, Observable, of, shareReplay, Subject, tap } from 'rxjs';
import { import {
AuthUser, AuthUser,
LoginResponse, LoginResponse,
@@ -64,6 +64,7 @@ export class Auth {
readonly sessionExpired$ = new Subject<void>(); readonly sessionExpired$ = new Subject<void>();
private refreshTimer: ReturnType<typeof setTimeout> | null = null; private refreshTimer: ReturnType<typeof setTimeout> | null = null;
private refreshInFlight$: Observable<ResponseResult<TokenRefreshResponse>> | null = null;
// --------------- Accessors --------------- // --------------- Accessors ---------------
@@ -138,15 +139,17 @@ export class Auth {
return of({ success: false, message: 'No refresh token.' }); return of({ success: false, message: 'No refresh token.' });
} }
if (this._isRefreshing()) { // If a refresh is already in flight, share the same observable
return EMPTY; // so all callers wait for the single refresh and only one
// refresh token rotation happens on the backend.
if (this.refreshInFlight$) {
return this.refreshInFlight$;
} }
this._isRefreshing.set(true); this._isRefreshing.set(true);
return this.api.refreshToken({ refreshToken: token }).pipe( this.refreshInFlight$ = this.api.refreshToken({ refreshToken: token }).pipe(
tap((res) => { tap((res) => {
this._isRefreshing.set(false);
if (res.success && res.data) { if (res.success && res.data) {
this.storeTokens(res.data.jwt, res.data.newRefreshToken); this.storeTokens(res.data.jwt, res.data.newRefreshToken);
this.scheduleRefresh(res.data.jwtExpiresIn); this.scheduleRefresh(res.data.jwtExpiresIn);
@@ -155,11 +158,17 @@ export class Auth {
} }
}), }),
catchError(() => { catchError(() => {
this._isRefreshing.set(false);
this.forceLogout(); this.forceLogout();
return of({ success: false, message: 'Token refresh failed.' } as ResponseResult<TokenRefreshResponse>); return of({ success: false, message: 'Token refresh failed.' } as ResponseResult<TokenRefreshResponse>);
}), }),
finalize(() => {
this._isRefreshing.set(false);
this.refreshInFlight$ = null;
}),
shareReplay(1),
); );
return this.refreshInFlight$;
} }
// --------------- Logout --------------- // --------------- Logout ---------------
@@ -238,6 +247,7 @@ export class Auth {
private clearState(): void { private clearState(): void {
this.clearRefreshTimer(); this.clearRefreshTimer();
this.refreshInFlight$ = null;
localStorage.removeItem(STORAGE_KEYS.JWT); localStorage.removeItem(STORAGE_KEYS.JWT);
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN); localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
localStorage.removeItem(STORAGE_KEYS.SESSION_TOKEN); localStorage.removeItem(STORAGE_KEYS.SESSION_TOKEN);