(() => {
const ACCESS_TOKEN_KEY = 'tiku_platform_access_token';
const REFRESH_TOKEN_KEY = 'tiku_platform_refresh_token';
const ACCESS_EXPIRES_KEY = 'tiku_platform_access_expires_at';
const USER_KEY = 'tiku_platform_user';
const runtime = window.GONGXUE_PLATFORM_RUNTIME_CONFIG || {};
let challengeToken = '';
let pendingResolve;
function apiUrl(path) {
return `${String(runtime.apiBaseUrl || '').replace(/\/+$/, '')}${path}`;
}
async function post(path, body) {
const response = await fetch(apiUrl(path), {
method: 'POST',
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
credentials: 'omit',
body: JSON.stringify(body),
});
const text = await response.text();
let payload = {};
if (text) {
try { payload = JSON.parse(text); } catch { payload = { detail: text.slice(0, 500) }; }
}
if (!response.ok) throw new Error(payload.detail || payload.title || payload.message || `请求失败(HTTP ${response.status})`);
return payload;
}
function gate() { return document.querySelector('#platformAuthGate'); }
function shell(title, description, content) {
gate().innerHTML = ``;
gate().hidden = false;
document.querySelector('.platform-app')?.setAttribute('inert', '');
}
function showError(error) {
const target = document.querySelector('#platformAuthError');
if (target) target.textContent = error?.message || '操作失败,请重试';
}
function setBusy(form, busy) {
form.querySelectorAll('button,input').forEach(control => { control.disabled = busy; });
}
function storeAuthenticated(result) {
const user = result?.user;
const tokens = user?.tokens;
if (!tokens?.accessToken || !tokens?.refreshToken) throw new Error('登录响应缺少令牌');
sessionStorage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken);
sessionStorage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken);
sessionStorage.setItem(ACCESS_EXPIRES_KEY, tokens.accessTokenExpiresAt || '');
sessionStorage.setItem(USER_KEY, JSON.stringify({ userId: user.userId, email: user.email, name: user.name }));
}
function completeGate() {
gate().hidden = true;
gate().innerHTML = '';
document.querySelector('.platform-app')?.removeAttribute('inert');
pendingResolve?.(true);
pendingResolve = null;
}
function finishAuthentication(result) {
storeAuthenticated(result);
completeGate();
}
async function handleAuthenticationResult(result) {
challengeToken = result.challengeToken || '';
if (result.status === 'authenticated') { finishAuthentication(result); return; }
if (result.status === 'password_change_required') { renderPasswordChange(); return; }
throw new Error(`不支持的认证状态:${result.status || 'unknown'}`);
}
function renderLogin() {
shell('平台管理员登录', '连接真实 PostgreSQL 与 ASP.NET Core API。', `
`);
document.querySelector('#platformLoginForm').addEventListener('submit', async event => {
event.preventDefault();
const form = event.currentTarget;
setBusy(form, true);
try {
const result = await post('/api/auth/login/password', { realm: 'platform', identifier: form.elements.identifier.value.trim(), password: form.elements.password.value });
await handleAuthenticationResult(result);
} catch (error) { showError(error); setBusy(form, false); }
});
}
function renderPasswordChange() {
shell('设置正式密码', '至少 8 位,且必须同时包含字母和数字。', ``);
document.querySelector('#platformPasswordForm').addEventListener('submit', async event => {
event.preventDefault();
const form = event.currentTarget;
if (form.elements.newPassword.value !== form.elements.confirmPassword.value) { showError(new Error('两次输入的密码不一致')); return; }
setBusy(form, true);
try { await handleAuthenticationResult(await post('/api/auth/password/change-required', { challengeToken, newPassword: form.elements.newPassword.value })); }
catch (error) { showError(error); setBusy(form, false); }
});
}
function clearSession() {
[ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, ACCESS_EXPIRES_KEY, USER_KEY].forEach(key => sessionStorage.removeItem(key));
}
function hasSession() {
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
const expiresAt = Date.parse(sessionStorage.getItem(ACCESS_EXPIRES_KEY) || '');
return Boolean(token) && Number.isFinite(expiresAt) && expiresAt > Date.now() + 15_000;
}
async function requireSession() {
if (hasSession()) return true;
clearSession();
renderLogin();
return new Promise(resolve => { pendingResolve = resolve; });
}
function requireReauthentication() {
clearSession();
renderLogin();
pendingResolve = () => location.reload();
}
async function logout() {
const refreshToken = sessionStorage.getItem(REFRESH_TOKEN_KEY);
try { if (refreshToken) await post('/api/auth/logout', { refreshToken }); } catch { /* Clear the browser session even when revocation cannot be reached. */ }
clearSession();
location.reload();
}
window.GongxuePlatformAuth = Object.freeze({
getAccessToken: () => sessionStorage.getItem(ACCESS_TOKEN_KEY) || '',
getUser: () => {
try { return JSON.parse(sessionStorage.getItem(USER_KEY) || 'null'); } catch { return null; }
},
logout,
requireReauthentication,
requireSession,
});
})();