mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-18 06:33:43 +08:00
254 lines
9.8 KiB
JavaScript
254 lines
9.8 KiB
JavaScript
import { getDatabase } from '../../../utils/databaseAdapter.js';
|
|
import { hashPassword, isHashed } from '../../../utils/auth/passwordHash.js';
|
|
import { destroySessionsByAuthType } from '../../../utils/auth/sessionManager.js';
|
|
import { normalizeSessionMaxAgeDays } from '../../../utils/auth/sessionConfig.js';
|
|
|
|
export async function onRequest(context) {
|
|
// 安全设置相关,GET方法读取设置,POST方法保存设置
|
|
const {
|
|
request, // same as existing Worker API
|
|
env, // same as existing Worker API
|
|
params, // if filename includes [id] or [[path]]
|
|
waitUntil, // same as ctx.waitUntil in existing Worker API
|
|
next, // used for middleware or to fetch assets
|
|
data, // arbitrary space for passing data between middlewares
|
|
} = context;
|
|
|
|
const db = getDatabase(env);
|
|
|
|
// GET读取设置
|
|
if (request.method === 'GET') {
|
|
const settings = await getSecurityConfig(db, env)
|
|
|
|
// 对前端隐藏实际密码值,返回占位符
|
|
// 前端只有在用户修改密码时才会发送新密码
|
|
const maskedSettings = JSON.parse(JSON.stringify(settings));
|
|
if (maskedSettings.auth.user?.authCode) {
|
|
maskedSettings.auth.user._hasPassword = true;
|
|
maskedSettings.auth.user.authCode = ''; // 不向前端暴露密码/哈希
|
|
}
|
|
if (maskedSettings.auth.admin?.adminPassword) {
|
|
maskedSettings.auth.admin._hasPassword = true;
|
|
maskedSettings.auth.admin.adminPassword = ''; // 不向前端暴露密码/哈希
|
|
}
|
|
|
|
return new Response(JSON.stringify(maskedSettings), {
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
})
|
|
}
|
|
|
|
// POST保存设置
|
|
if (request.method === 'POST') {
|
|
const settings = await getSecurityConfig(db, env) // 先读取已有设置,再进行覆盖
|
|
|
|
const body = await request.json()
|
|
const newSettings = body
|
|
|
|
// 覆盖设置,apiTokens不在这里修改
|
|
settings.upload = newSettings.upload || settings.upload
|
|
settings.access = newSettings.access || settings.access
|
|
settings.access.userSessionMaxAge = normalizeSessionMaxAgeDays(settings.access.userSessionMaxAge)
|
|
settings.access.adminSessionMaxAge = normalizeSessionMaxAgeDays(settings.access.adminSessionMaxAge)
|
|
settings.access.imageTransformEnabled = settings.access.imageTransformEnabled === true
|
|
|
|
const allowedSizes = normalizeImageTransformAllowedSizes(settings.access.imageTransformAllowedSizes)
|
|
if (allowedSizes.error) {
|
|
return new Response(JSON.stringify({ error: allowedSizes.error }), {
|
|
status: 400,
|
|
headers: { 'content-type': 'application/json' },
|
|
})
|
|
}
|
|
settings.access.imageTransformAllowedSizes = allowedSizes.value
|
|
|
|
// 处理认证设置:空密码表示不修改,_clear 标记表示清除密码
|
|
let userPasswordChanged = false;
|
|
let adminPasswordChanged = false;
|
|
|
|
if (newSettings.auth) {
|
|
if (newSettings.auth.user) {
|
|
if (newSettings.auth.user._clear) {
|
|
// 显式清除密码
|
|
newSettings.auth.user.authCode = '';
|
|
userPasswordChanged = true;
|
|
} else if (newSettings.auth.user.authCode === '' || newSettings.auth.user.authCode === undefined) {
|
|
// 密码为空,保留原密码
|
|
newSettings.auth.user.authCode = settings.auth.user.authCode;
|
|
} else {
|
|
userPasswordChanged = true;
|
|
}
|
|
delete newSettings.auth.user._clear;
|
|
settings.auth.user = newSettings.auth.user;
|
|
}
|
|
if (newSettings.auth.admin) {
|
|
if (newSettings.auth.admin._clear) {
|
|
// 显式清除密码和用户名
|
|
newSettings.auth.admin.adminPassword = '';
|
|
newSettings.auth.admin.adminUsername = '';
|
|
adminPasswordChanged = true;
|
|
} else if (newSettings.auth.admin.adminPassword === '' || newSettings.auth.admin.adminPassword === undefined) {
|
|
// 密码为空,保留原密码
|
|
newSettings.auth.admin.adminPassword = settings.auth.admin.adminPassword;
|
|
} else {
|
|
adminPasswordChanged = true;
|
|
}
|
|
delete newSettings.auth.admin._clear;
|
|
if (newSettings.auth.admin.adminUsername !== undefined) {
|
|
settings.auth.admin.adminUsername = newSettings.auth.admin.adminUsername;
|
|
}
|
|
settings.auth.admin.adminPassword = newSettings.auth.admin.adminPassword;
|
|
}
|
|
}
|
|
|
|
// 对密码进行哈希处理(如果是新的明文密码)
|
|
if (settings.auth.user?.authCode && !isHashed(settings.auth.user.authCode)) {
|
|
settings.auth.user.authCode = await hashPassword(settings.auth.user.authCode);
|
|
}
|
|
if (settings.auth.admin?.adminPassword && !isHashed(settings.auth.admin.adminPassword)) {
|
|
settings.auth.admin.adminPassword = await hashPassword(settings.auth.admin.adminPassword);
|
|
}
|
|
|
|
// 清理前端标记字段
|
|
delete settings.auth.user?._hasPassword;
|
|
delete settings.auth.admin?._hasPassword;
|
|
|
|
// 写入数据库
|
|
await db.put('manage@sysConfig@security', JSON.stringify(settings))
|
|
|
|
// 密码变更后清除对应类型的所有会话
|
|
if (userPasswordChanged) {
|
|
await destroySessionsByAuthType(env, 'user');
|
|
}
|
|
if (adminPasswordChanged) {
|
|
await destroySessionsByAuthType(env, 'admin');
|
|
}
|
|
|
|
return new Response(JSON.stringify({
|
|
message: 'security settings saved',
|
|
userPasswordChanged,
|
|
adminPasswordChanged,
|
|
}), {
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
export async function getSecurityConfig(db, env) {
|
|
const settings = {}
|
|
// 读取数据库中的设置
|
|
const settingsStr = await db.get('manage@sysConfig@security')
|
|
const settingsKV = settingsStr ? JSON.parse(settingsStr) : {}
|
|
|
|
// 认证管理
|
|
const kvAuth = settingsKV.auth || {}
|
|
const auth = {
|
|
user: {
|
|
authCode: kvAuth.user?.authCode ?? env.AUTH_CODE ?? '',
|
|
},
|
|
admin: {
|
|
adminUsername: kvAuth.admin?.adminUsername ?? env.BASIC_USER ?? '',
|
|
adminPassword: kvAuth.admin?.adminPassword ?? env.BASIC_PASS ?? '',
|
|
}
|
|
}
|
|
settings.auth = auth
|
|
|
|
// 上传管理
|
|
const kvUpload = settingsKV.upload || {}
|
|
const upload = {
|
|
moderate: {
|
|
enabled: kvUpload.moderate?.enabled ?? false,
|
|
channel: kvUpload.moderate?.channel || 'moderatecontent.com', // [moderatecontent.com, nsfwjs]
|
|
moderateContentApiKey: kvUpload.moderate?.moderateContentApiKey || kvUpload.moderate?.apiKey || env.ModerateContentApiKey || '',
|
|
nsfwApiPath: kvUpload.moderate?.nsfwApiPath || '',
|
|
},
|
|
ipQuery: {
|
|
enabled: kvUpload.ipQuery?.enabled ?? false,
|
|
channel: kvUpload.ipQuery?.channel || 'customApi',
|
|
customApi: {
|
|
url: kvUpload.ipQuery?.customApi?.url || '',
|
|
params: normalizeIpQueryParams(kvUpload.ipQuery?.customApi?.params),
|
|
responseFields: normalizeIpQueryResponseFields(kvUpload.ipQuery?.customApi?.responseFields)
|
|
}
|
|
}
|
|
}
|
|
settings.upload = upload
|
|
|
|
// 访问管理
|
|
const kvAccess = settingsKV.access || {}
|
|
const access = {
|
|
allowedDomains: kvAccess.allowedDomains || env.ALLOWED_DOMAINS || '',
|
|
whiteListMode: kvAccess.whiteListMode ?? env.WhiteList_Mode === 'true',
|
|
imageTransformEnabled: kvAccess.imageTransformEnabled === true,
|
|
imageTransformAllowedSizes: normalizeImageTransformAllowedSizes(kvAccess.imageTransformAllowedSizes).value,
|
|
// 新增会话安全策略字段
|
|
sessionSecure: kvAccess.sessionSecure ?? false,
|
|
userSessionMaxAge: normalizeSessionMaxAgeDays(kvAccess.userSessionMaxAge ?? 14),
|
|
adminSessionMaxAge: normalizeSessionMaxAgeDays(kvAccess.adminSessionMaxAge ?? 14),
|
|
}
|
|
settings.access = access
|
|
|
|
// API Token 管理
|
|
const kvApiTokens = settingsKV.apiTokens || {}
|
|
const apiTokens = {
|
|
tokens: kvApiTokens.tokens || {}
|
|
}
|
|
settings.apiTokens = apiTokens
|
|
|
|
return settings;
|
|
}
|
|
|
|
function normalizeImageTransformAllowedSizes(value) {
|
|
if (value === undefined || value === null || String(value).trim() === '') {
|
|
return { value: '' }
|
|
}
|
|
|
|
const sizes = String(value)
|
|
.split(',')
|
|
.map(size => size.trim().toLowerCase())
|
|
.filter(Boolean)
|
|
const normalized = []
|
|
|
|
for (const size of sizes) {
|
|
const match = size.match(/^(auto|[1-9]\d*)x(auto|[1-9]\d*)$/)
|
|
if (!match || (match[1] === 'auto' && match[2] === 'auto')) {
|
|
return { error: `Invalid image transform size: ${size}` }
|
|
}
|
|
|
|
for (const dimension of match.slice(1)) {
|
|
if (dimension !== 'auto' && Number(dimension) > 4096) {
|
|
return { error: `Invalid image transform size: ${size}` }
|
|
}
|
|
}
|
|
|
|
if (!normalized.includes(size)) normalized.push(size)
|
|
}
|
|
|
|
return { value: normalized.join(',') }
|
|
}
|
|
|
|
function normalizeIpQueryParams(params) {
|
|
if (!Array.isArray(params) || params.length === 0) {
|
|
return [{ key: 'ip', value: '{ip}' }];
|
|
}
|
|
|
|
return params.map(param => ({
|
|
key: param?.key || '',
|
|
value: param?.value || ''
|
|
}));
|
|
}
|
|
|
|
function normalizeIpQueryResponseFields(fields) {
|
|
if (!Array.isArray(fields)) {
|
|
return [];
|
|
}
|
|
|
|
return fields.map(field => {
|
|
if (typeof field === 'string') return field;
|
|
return field?.path || '';
|
|
});
|
|
}
|