mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-16 05:33:44 +08:00
226 lines
6.9 KiB
JavaScript
226 lines
6.9 KiB
JavaScript
/**
|
||
* 会话管理工具
|
||
* 使用数据库存储会话,通过 HttpOnly Cookie 传递会话 Token
|
||
* 管理端和用户端使用独立的 Cookie(admin_session / user_session)
|
||
*/
|
||
|
||
import { generateSessionToken } from './passwordHash.js';
|
||
import { getDatabase } from '../databaseAdapter.js';
|
||
import { fetchSecurityConfig } from '../sysConfig.js';
|
||
import { normalizeSessionMaxAgeDays, sessionMaxAgeDaysToTtl } from './sessionConfig.js';
|
||
|
||
const SESSION_PREFIX = 'manage@session@';
|
||
|
||
// Cookie 名称映射
|
||
const COOKIE_NAMES = {
|
||
admin: 'admin_session',
|
||
user: 'user_session',
|
||
};
|
||
|
||
/**
|
||
* 创建新会话
|
||
* @param {Object} env - 环境变量
|
||
* @param {string} authType - 认证类型 ('admin' | 'user')
|
||
* @param {string} [username] - 用户名(管理员登录时使用)
|
||
* @returns {Promise<{token: string, cookie: string}>}
|
||
*/
|
||
export async function createSession(env, authType, username = '') {
|
||
// 读取安全策略配置
|
||
const securityConfig = await fetchSecurityConfig(env);
|
||
const accessConfig = securityConfig.access || {};
|
||
const secure = accessConfig.sessionSecure ?? false;
|
||
const rawMaxAgeDays = authType === 'admin'
|
||
? (accessConfig.adminSessionMaxAge ?? 14)
|
||
: (accessConfig.userSessionMaxAge ?? 14);
|
||
const maxAgeDays = normalizeSessionMaxAgeDays(rawMaxAgeDays);
|
||
const maxAge = sessionMaxAgeDaysToTtl(maxAgeDays);
|
||
|
||
const db = getDatabase(env);
|
||
const token = generateSessionToken();
|
||
const sessionData = {
|
||
authType,
|
||
username,
|
||
createdAt: Date.now(),
|
||
expiresAt: Date.now() + maxAge * 1000,
|
||
};
|
||
|
||
await db.put(`${SESSION_PREFIX}${token}`, JSON.stringify(sessionData), {
|
||
expirationTtl: maxAge,
|
||
});
|
||
|
||
const cookieName = COOKIE_NAMES[authType] || 'session';
|
||
const cookie = buildSessionCookie(cookieName, token, maxAge, secure);
|
||
return { token, cookie };
|
||
}
|
||
|
||
/**
|
||
* 验证会话(按 authType 读取对应的 Cookie)
|
||
* @param {Object} env - 环境变量
|
||
* @param {Request} request - 请求对象
|
||
* @param {string} authType - 要验证的认证类型 ('admin' | 'user')
|
||
* @returns {Promise<{valid: boolean, session?: Object}>}
|
||
*/
|
||
export async function validateSession(env, request, authType) {
|
||
const cookieName = COOKIE_NAMES[authType] || 'session';
|
||
const token = getCookieValue(request, cookieName);
|
||
if (!token) {
|
||
return { valid: false };
|
||
}
|
||
|
||
const db = getDatabase(env);
|
||
const sessionStr = await db.get(`${SESSION_PREFIX}${token}`);
|
||
if (!sessionStr) {
|
||
return { valid: false };
|
||
}
|
||
|
||
try {
|
||
const session = JSON.parse(sessionStr);
|
||
// 验证 authType 匹配
|
||
if (session.authType !== authType) {
|
||
return { valid: false };
|
||
}
|
||
if (Date.now() > session.expiresAt) {
|
||
await db.delete(`${SESSION_PREFIX}${token}`);
|
||
return { valid: false };
|
||
}
|
||
return { valid: true, session };
|
||
} catch {
|
||
return { valid: false };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证任意有效会话(不限 authType,用于 sessionCheck 接口)
|
||
* @param {Object} env - 环境变量
|
||
* @param {Request} request - 请求对象
|
||
* @returns {Promise<{valid: boolean, session?: Object}>}
|
||
*/
|
||
export async function validateAnySession(env, request) {
|
||
// 优先检查 admin,再检查 user
|
||
const adminResult = await validateSession(env, request, 'admin');
|
||
if (adminResult.valid) return adminResult;
|
||
|
||
const userResult = await validateSession(env, request, 'user');
|
||
if (userResult.valid) return userResult;
|
||
|
||
return { valid: false };
|
||
}
|
||
|
||
/**
|
||
* 销毁会话
|
||
* @param {Object} env - 环境变量
|
||
* @param {Request} request - 请求对象
|
||
* @param {string} [authType] - 要销毁的认证类型,不传则销毁所有
|
||
* @returns {Promise<string|string[]>} 清除 Cookie 的 Set-Cookie 头
|
||
*/
|
||
export async function destroySession(env, request, authType) {
|
||
// 读取安全策略配置
|
||
const securityConfig = await fetchSecurityConfig(env);
|
||
const secure = securityConfig.access?.sessionSecure ?? false;
|
||
|
||
const db = getDatabase(env);
|
||
|
||
if (authType) {
|
||
// 销毁指定类型的会话
|
||
const cookieName = COOKIE_NAMES[authType] || 'session';
|
||
const token = getCookieValue(request, cookieName);
|
||
if (token) {
|
||
await db.delete(`${SESSION_PREFIX}${token}`);
|
||
}
|
||
return buildSessionCookie(cookieName, '', 0, secure);
|
||
} else {
|
||
// 销毁所有类型的会话
|
||
const cookies = [];
|
||
for (const [type, cookieName] of Object.entries(COOKIE_NAMES)) {
|
||
const token = getCookieValue(request, cookieName);
|
||
if (token) {
|
||
await db.delete(`${SESSION_PREFIX}${token}`);
|
||
}
|
||
cookies.push(buildSessionCookie(cookieName, '', 0, secure));
|
||
}
|
||
return cookies;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 按认证类型批量清除会话
|
||
* @param {Object} env - 环境变量
|
||
* @param {string} authType - 要清除的认证类型 ('admin' | 'user')
|
||
* @returns {Promise<number>} 清除的会话数量
|
||
*/
|
||
export async function destroySessionsByAuthType(env, authType) {
|
||
const db = getDatabase(env);
|
||
let destroyed = 0;
|
||
|
||
let cursor = undefined;
|
||
let hasMore = true;
|
||
|
||
while (hasMore) {
|
||
const listOptions = { prefix: SESSION_PREFIX };
|
||
if (cursor) {
|
||
listOptions.cursor = cursor;
|
||
}
|
||
|
||
const result = await db.list(listOptions);
|
||
const keys = result.keys || [];
|
||
|
||
for (const key of keys) {
|
||
try {
|
||
const sessionStr = await db.get(key.name);
|
||
if (sessionStr) {
|
||
const session = JSON.parse(sessionStr);
|
||
if (session.authType === authType) {
|
||
await db.delete(key.name);
|
||
destroyed++;
|
||
}
|
||
}
|
||
} catch {
|
||
await db.delete(key.name);
|
||
destroyed++;
|
||
}
|
||
}
|
||
|
||
cursor = result.cursor;
|
||
hasMore = !result.list_complete && cursor;
|
||
}
|
||
|
||
return destroyed;
|
||
}
|
||
|
||
/**
|
||
* 从请求中提取指定 Cookie 的值
|
||
* @param {Request} request - 请求对象
|
||
* @param {string} name - Cookie 名称
|
||
* @returns {string|null}
|
||
*/
|
||
function getCookieValue(request, name) {
|
||
const cookieHeader = request.headers.get('Cookie');
|
||
if (!cookieHeader) return null;
|
||
|
||
const regex = new RegExp('(^|;\\s*)' + name + '=([^;]+)');
|
||
const match = cookieHeader.match(regex);
|
||
return match ? match[2] : null;
|
||
}
|
||
|
||
/**
|
||
* 构建 Set-Cookie 头的值
|
||
* @param {string} name - Cookie 名称
|
||
* @param {string} token - 会话 Token
|
||
* @param {number} maxAge - 最大存活时间(秒)
|
||
* @param {boolean} secure - 是否添加 Secure 属性
|
||
* @returns {string}
|
||
*/
|
||
function buildSessionCookie(name, token, maxAge, secure = false) {
|
||
const parts = [
|
||
`${name}=${token}`,
|
||
`Path=/`,
|
||
`HttpOnly`,
|
||
`SameSite=Strict`,
|
||
`Max-Age=${maxAge}`,
|
||
];
|
||
if (secure) {
|
||
parts.push('Secure');
|
||
}
|
||
return parts.join('; ');
|
||
}
|