feat(auth): harden token lifecycle and password policy

This commit is contained in:
yoyuzh
2026-03-19 14:51:18 +08:00
parent 41a83d2805
commit a78d0dc2db
26 changed files with 1047 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
import type { AuthSession } from './types';
import type { AuthResponse, AuthSession } from './types';
const SESSION_STORAGE_KEY = 'portal-session';
const POST_LOGIN_PENDING_KEY = 'portal-post-login-pending';
@@ -10,6 +10,40 @@ function notifySessionChanged() {
}
}
function normalizeSession(value: unknown): AuthSession | null {
if (!value || typeof value !== 'object') {
return null;
}
const candidate = value as Partial<AuthSession> & {accessToken?: string};
const token = typeof candidate.token === 'string' && candidate.token.trim()
? candidate.token
: typeof candidate.accessToken === 'string' && candidate.accessToken.trim()
? candidate.accessToken
: null;
if (!token || !candidate.user) {
return null;
}
return {
token,
refreshToken:
typeof candidate.refreshToken === 'string' && candidate.refreshToken.trim()
? candidate.refreshToken
: null,
user: candidate.user,
};
}
export function createSession(auth: AuthResponse): AuthSession {
return {
token: auth.accessToken || auth.token,
refreshToken: auth.refreshToken ?? null,
user: auth.user,
};
}
export function readStoredSession(): AuthSession | null {
if (typeof localStorage === 'undefined') {
return null;
@@ -21,7 +55,11 @@ export function readStoredSession(): AuthSession | null {
}
try {
return JSON.parse(rawValue) as AuthSession;
const session = normalizeSession(JSON.parse(rawValue));
if (!session) {
localStorage.removeItem(SESSION_STORAGE_KEY);
}
return session;
} catch {
localStorage.removeItem(SESSION_STORAGE_KEY);
return null;