Files
sproutblog/src/lib/auth.ts
2026-06-24 22:10:27 +08:00

79 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cleanText } from "./text";
import { getAdminPassword, unauthorized } from "./http";
export function parseCookie(header: string): Record<string, string> {
const out: Record<string, string> = {};
if (!header) return out;
for (const part of header.split(";")) {
const idx = part.indexOf("=");
if (idx === -1) continue;
const k = part.slice(0, idx).trim();
const v = part.slice(idx + 1).trim();
try {
out[k] = decodeURIComponent(v);
} catch {
out[k] = v;
}
}
return out;
}
export async function adminSessionCookieValue(env: Env): Promise<string> {
const password = getAdminPassword(env);
const enc = new TextEncoder();
const data = enc.encode(`${password}\0sproutblog_admin_v1`);
const hash = await crypto.subtle.digest("SHA-256", data);
return [...new Uint8Array(hash)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
/**
* 需要后台权限的接口:未配置密码则放行;否则校验 Cookie 或 Basic Auth。
* 返回 null 表示已通过;返回 Response 为 401。
*/
export async function requireAdmin(
request: Request,
env: Env
): Promise<Response | null> {
const password = getAdminPassword(env);
if (!password) return null;
const cookies = parseCookie(request.headers.get("Cookie") || "");
const expected = await adminSessionCookieValue(env);
if (cookies.sb_admin && cookies.sb_admin === expected) {
return null;
}
const header = request.headers.get("Authorization") || "";
if (header.startsWith("Basic ")) {
try {
const decoded = atob(header.slice(6));
const split = decoded.indexOf(":");
const inputPassword = split >= 0 ? decoded.slice(split + 1) : "";
if (inputPassword === password) return null;
} catch {
/* ignore */
}
}
return unauthorized();
}
/** 已配置密码且当前请求具备后台凭证Cookie / Basic */
export async function hasValidAdminSession(
request: Request,
env: Env
): Promise<boolean> {
const password = getAdminPassword(env);
if (!password) return false;
return (await requireAdmin(request, env)) === null;
}
/** 是否可列出/阅读草稿(未配置密码时保持原先「全量可见」行为) */
export async function canAccessDrafts(request: Request, env: Env): Promise<boolean> {
const password = getAdminPassword(env);
if (!password) return true;
return (await requireAdmin(request, env)) === null;
}