fix(admin): keep admin panel logged in — refresh the token instead of dying at 7 days
CI/CD / CI · API (dotnet build + test) (push) Successful in 39s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m6s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 2m35s
CI/CD / CI · API (dotnet build + test) (push) Successful in 39s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m6s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 2m35s
Prod diag showed every /api/admin/* call returning 401 with "IDX10223: token expired, ValidTo 06/09" — the admin access token was 6 days dead and nothing renewed it, so cafes/tickets/integrations/settings all loaded empty. The admin web (unlike the café dashboard) had NO refresh logic at all: it only ever sent the access token, and its 401 handler early-returned on any error code before the login redirect, so the admin wasn't even bounced to login — pages just showed no data. Client (admin-client.ts): add a silent refresh-on-401 mirroring the dashboard — one shared in-flight POST /api/admin/auth/refresh for a burst of 401s, replay the original request on success, force-logout only on a definitive 4xx, and ride out a transient failure (API restarting during deploy) without logging out. Backend (AdminAuthService): make refresh non-rotating + sliding (reuse the presented refresh token and re-store it) instead of revoke-and-mint, so the dashboard's many concurrent refreshes don't race the rotated token — same fix already applied to the main API. Also bump admin tokens 7d/30d → 30d/365d to match the main API, so the session is long-lived even before the first refresh round-trip. tsc clean; Admin.API builds clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -145,8 +145,11 @@ public class AdminAuthService : IAdminAuthService
|
||||
if (admin is null)
|
||||
return (false, null, "NOT_FOUND", "Admin no longer exists.");
|
||||
|
||||
await _refreshTokenStore.RevokeAsync(request.RefreshToken, cancellationToken);
|
||||
var tokens = await IssueTokensAsync(admin, cancellationToken);
|
||||
// Non-rotating sliding refresh: reuse the presented token (re-stored to
|
||||
// slide its TTL) instead of revoking + minting a new one. Rotation here
|
||||
// raced across the admin dashboard's many concurrent calls and logged
|
||||
// the admin out; reuse makes concurrent refreshes idempotent.
|
||||
var tokens = await IssueTokensAsync(admin, cancellationToken, existingRefreshToken: request.RefreshToken);
|
||||
return (true, tokens, null, null);
|
||||
}
|
||||
|
||||
@@ -195,10 +198,13 @@ public class AdminAuthService : IAdminAuthService
|
||||
|
||||
private async Task<AuthTokenResponse> IssueTokensAsync(
|
||||
Core.Entities.SystemAdmin admin,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken,
|
||||
string? existingRefreshToken = null)
|
||||
{
|
||||
var accessToken = _jwtTokenService.CreateAdminAccessToken(admin);
|
||||
var refreshToken = _jwtTokenService.CreateRefreshToken();
|
||||
// Mint a fresh token only on a real login (existingRefreshToken == null);
|
||||
// a refresh reuses + re-stores the presented token to slide its TTL.
|
||||
var refreshToken = existingRefreshToken ?? _jwtTokenService.CreateRefreshToken();
|
||||
var refreshDays = _configuration.GetValue("Jwt:RefreshTokenExpiryDays", 30);
|
||||
|
||||
await _refreshTokenStore.StoreAsync(
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"Key": "meezi-dev-secret-key-min-32-chars!!",
|
||||
"Issuer": "meezi",
|
||||
"Audience": "meezi-admin",
|
||||
"AccessTokenExpiryDays": 7,
|
||||
"RefreshTokenExpiryDays": 30
|
||||
"AccessTokenExpiryDays": 30,
|
||||
"RefreshTokenExpiryDays": 365
|
||||
},
|
||||
"Cors": {
|
||||
"Origins": [
|
||||
|
||||
Reference in New Issue
Block a user