94 lines
3.4 KiB
JavaScript
94 lines
3.4 KiB
JavaScript
(() => {
|
|
const form = document.getElementById("client-auth");
|
|
const message = document.getElementById("client-auth-status");
|
|
const loginButton = form.querySelector('button[type="submit"]');
|
|
const checkButton = document.getElementById("client-auth-check");
|
|
const refreshButton = document.getElementById("client-auth-refresh");
|
|
let accessToken;
|
|
let refreshToken;
|
|
let accessExpiry = 0;
|
|
|
|
async function requestToken(operation, data) {
|
|
const response = await fetch("/api/v1/auth/" + operation, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(data),
|
|
});
|
|
const result = await response.json();
|
|
if (!response.ok || result.code !== 0) throw new Error(result.message || "鉴权失败");
|
|
accessToken = result.data.access_token;
|
|
accessExpiry = Date.now() + result.data.expires_in * 1000;
|
|
return result.data;
|
|
}
|
|
|
|
async function runAction(action) {
|
|
loginButton.disabled = checkButton.disabled = refreshButton.disabled = true;
|
|
try {
|
|
await action();
|
|
} catch (error) {
|
|
message.textContent = error.message;
|
|
} finally {
|
|
loginButton.disabled = checkButton.disabled = false;
|
|
refreshButton.disabled = !refreshToken;
|
|
}
|
|
}
|
|
|
|
form.addEventListener("submit", (event) => {
|
|
event.preventDefault();
|
|
const data = new FormData(form);
|
|
form.elements.namedItem("password").value = "";
|
|
runAction(async () => {
|
|
const result = await requestToken("login", {
|
|
username: data.get("username"),
|
|
password: data.get("password"),
|
|
});
|
|
refreshToken = result.refresh_token;
|
|
message.textContent = "已登录。访问令牌过期时会自动刷新。";
|
|
});
|
|
});
|
|
|
|
async function refresh() {
|
|
if (!refreshToken) throw new Error("请先登录,再提交图片。");
|
|
try {
|
|
await requestToken("refresh", { refresh_token: refreshToken });
|
|
} catch (error) {
|
|
accessToken = refreshToken = undefined;
|
|
refreshButton.disabled = true;
|
|
message.textContent = "请重新登录。";
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
checkButton.addEventListener("click", () => runAction(async () => {
|
|
const path = "/api/v1/auth/check";
|
|
const anonymous = await fetch(path);
|
|
if (anonymous.status !== 401) throw new Error("无令牌检查返回 HTTP " + anonymous.status);
|
|
if (!accessToken) {
|
|
message.textContent = "无令牌已拒绝(HTTP 401)。登录后可验证访问令牌。";
|
|
return;
|
|
}
|
|
const response = await fetch(path, { headers: { Authorization: "Bearer " + accessToken } });
|
|
const result = await response.json();
|
|
if (!response.ok || result.code !== 0) throw new Error(result.message || "访问令牌检查失败");
|
|
message.textContent = "鉴权通过:无令牌返回 HTTP 401,有效令牌返回 HTTP 200。";
|
|
}));
|
|
|
|
refreshButton.addEventListener("click", () => runAction(async () => {
|
|
await refresh();
|
|
message.textContent = "访问令牌已刷新,有效期 15 分钟。";
|
|
}));
|
|
|
|
window.hairClientFetch = async (path, options) => {
|
|
if (Date.now() >= accessExpiry || !accessToken) await refresh();
|
|
const send = () => {
|
|
const headers = new Headers(options.headers);
|
|
headers.set("Authorization", "Bearer " + accessToken);
|
|
return fetch(path, { ...options, headers });
|
|
};
|
|
const response = await send();
|
|
if (response.status !== 401) return response;
|
|
await refresh();
|
|
return send();
|
|
};
|
|
})();
|