fix(auth): treat expired JWTs as no identity, improve logout and token verification flow, and bump deps

This commit is contained in:
2026-03-04 01:11:19 +00:00
parent 89b9d01628
commit 34d40f7370
11 changed files with 1232 additions and 1581 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '11.0.0',
version: '11.0.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -238,9 +238,12 @@ interface IActionContext {
}
const getActionContext = (): IActionContext => {
return {
identity: loginStatePart.getState().identity,
};
const identity = loginStatePart.getState().identity;
// Treat expired JWTs as no identity — prevents stale persisted sessions from firing requests
if (identity && identity.expiresAt && identity.expiresAt < Date.now()) {
return { identity: null };
}
return { identity };
};
// Login Action
@@ -271,24 +274,23 @@ export const loginAction = loginStatePart.createAction<{
}
});
// Logout Action
// Logout Action — always clears state, even if identity is expired/missing
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
if (!context.identity) return statePartArg.getState();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLogout
>('/typedrequest', 'adminLogout');
try {
await typedRequest.fire({
identity: context.identity,
});
} catch (error) {
console.error('Logout error:', error);
// Try to notify server, but don't block logout if identity is missing/expired
if (context.identity) {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLogout
>('/typedrequest', 'adminLogout');
try {
await typedRequest.fire({ identity: context.identity });
} catch (error) {
console.error('Logout error:', error);
}
}
// Clear login state regardless
// Always clear login state
return {
identity: null,
isLoggedIn: false,
@@ -1338,6 +1340,12 @@ async function dispatchCombinedRefreshAction() {
}
} catch (error) {
console.error('Combined refresh failed:', error);
// If the error looks like an auth failure (invalid JWT), force re-login
const errMsg = String(error);
if (errMsg.includes('invalid') || errMsg.includes('unauthorized') || errMsg.includes('401')) {
await loginStatePart.dispatchAction(logoutAction, null);
window.location.reload();
}
}
}

View File

@@ -1,5 +1,6 @@
import * as plugins from '../plugins.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';
import { appRouter } from '../router.js';
import {
@@ -218,13 +219,27 @@ export class OpsDashboard extends DeesElement {
// Handle initial state - check if we have a stored session that's still valid
const loginState = appstate.loginStatePart.getState();
if (loginState.identity?.jwt) {
// Verify JWT hasn't expired
if (loginState.identity.expiresAt > Date.now()) {
// JWT still valid, restore logged-in state
this.loginState = loginState;
await simpleLogin.switchToSlottedContent();
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
// Client-side expiry looks valid — verify with server (keypair may have changed)
try {
const verifyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_VerifyIdentity
>('/typedrequest', 'verifyIdentity');
const response = await verifyRequest.fire({ identity: loginState.identity });
if (response.valid) {
// JWT confirmed valid by server
this.loginState = loginState;
await simpleLogin.switchToSlottedContent();
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
} else {
// Server rejected the JWT — clear state, show login
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
}
} catch {
// Server unreachable or error — clear state, show login
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
}
} else {
// JWT expired, clear the stored state
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);