mirror of
https://github.com/MarSeventh/CloudFlare-ImgBed.git
synced 2026-04-24 22:25:07 +00:00
168 lines
5.1 KiB
JavaScript
168 lines
5.1 KiB
JavaScript
import { fetchSecurityConfig } from "../../utils/sysConfig";
|
||
import { checkDatabaseConfig } from "../../utils/middleware";
|
||
import { validateApiToken } from "../../utils/tokenValidator";
|
||
import { getDatabase } from "../../utils/databaseAdapter.js";
|
||
|
||
let securityConfig = {}
|
||
let basicUser = ""
|
||
let basicPass = ""
|
||
|
||
async function errorHandling(context) {
|
||
try {
|
||
return await context.next();
|
||
} catch (err) {
|
||
return new Response(`${err.message}\n${err.stack}`, { status: 500 });
|
||
}
|
||
}
|
||
|
||
function basicAuthentication(request) {
|
||
const Authorization = request.headers.get('Authorization');
|
||
|
||
const [scheme, encoded] = Authorization.split(' ');
|
||
|
||
// The Authorization header must start with Basic, followed by a space.
|
||
if (!encoded || scheme !== 'Basic') {
|
||
return BadRequestException('Malformed authorization header.');
|
||
}
|
||
|
||
// Decodes the base64 value and performs unicode normalization.
|
||
// @see https://datatracker.ietf.org/doc/html/rfc7613#section-3.3.2 (and #section-4.2.2)
|
||
// @see https://dev.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
|
||
const buffer = Uint8Array.from(atob(encoded), character => character.charCodeAt(0));
|
||
const decoded = new TextDecoder().decode(buffer).normalize();
|
||
|
||
// The username & password are split by the first colon.
|
||
//=> example: "username:password"
|
||
const index = decoded.indexOf(':');
|
||
|
||
// The user & password are split by the first colon and MUST NOT contain control characters.
|
||
// @see https://tools.ietf.org/html/rfc5234#appendix-B.1 (=> "CTL = %x00-1F / %x7F")
|
||
if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) {
|
||
return BadRequestException('Invalid authorization value.');
|
||
}
|
||
|
||
return {
|
||
user: decoded.substring(0, index),
|
||
pass: decoded.substring(index + 1),
|
||
};
|
||
}
|
||
|
||
function UnauthorizedException(reason) {
|
||
return new Response(reason, {
|
||
status: 401,
|
||
statusText: 'Unauthorized',
|
||
headers: {
|
||
'Content-Type': 'text/plain;charset=UTF-8',
|
||
// Disables caching by default.
|
||
'Cache-Control': 'no-store',
|
||
// Returns the "Content-Length" header for HTTP HEAD requests.
|
||
'Content-Length': reason.length,
|
||
},
|
||
});
|
||
}
|
||
|
||
function BadRequestException(reason) {
|
||
return new Response(reason, {
|
||
status: 400,
|
||
statusText: 'Bad Request',
|
||
headers: {
|
||
'Content-Type': 'text/plain;charset=UTF-8',
|
||
// Disables caching by default.
|
||
'Cache-Control': 'no-store',
|
||
// Returns the "Content-Length" header for HTTP HEAD requests.
|
||
'Content-Length': reason.length,
|
||
},
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* 根据请求路径提取所需权限
|
||
* @param {string} pathname - 请求路径
|
||
* @returns {string|null} 需要的权限类型或null
|
||
*/
|
||
function extractRequiredPermission(pathname) {
|
||
// 提取路径中的关键部分
|
||
const pathParts = pathname.toLowerCase().split('/');
|
||
|
||
// 检查是否包含delete路径
|
||
if (pathParts.includes('delete')) {
|
||
return 'delete';
|
||
}
|
||
|
||
// 检查是否包含list路径
|
||
if (pathParts.includes('list')) {
|
||
return 'list';
|
||
}
|
||
|
||
// 其他情况返回null
|
||
return null;
|
||
}
|
||
|
||
// CORS 跨域响应头
|
||
const corsHeaders = {
|
||
'Access-Control-Allow-Origin': '*',
|
||
'Access-Control-Allow-Methods': 'GET, POST, DELETE, PUT, OPTIONS',
|
||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||
'Access-Control-Max-Age': '86400',
|
||
};
|
||
|
||
async function authentication(context) {
|
||
// OPTIONS 预检请求不需要鉴权,直接返回 CORS 响应
|
||
// 这是安全的,因为 OPTIONS 请求只是预检请求,不会执行任何实际操作
|
||
if (context.request.method === 'OPTIONS') {
|
||
return new Response(null, {
|
||
status: 204,
|
||
headers: corsHeaders
|
||
});
|
||
}
|
||
|
||
// 读取安全配置
|
||
securityConfig = await fetchSecurityConfig(context.env);
|
||
basicUser = securityConfig.auth.admin.adminUsername
|
||
basicPass = securityConfig.auth.admin.adminPassword
|
||
|
||
if (typeof basicUser == "undefined" || basicUser == null || basicUser == "") {
|
||
// 无需身份验证
|
||
return context.next();
|
||
} else {
|
||
|
||
if (context.request.headers.has('Authorization')) {
|
||
// 首先尝试使用API Token验证
|
||
|
||
// 根据请求的 url 判断所需权限
|
||
const pathname = new URL(context.request.url).pathname;
|
||
const requiredPermission = extractRequiredPermission(pathname);
|
||
|
||
const db = getDatabase(context.env);
|
||
const tokenValidation = await validateApiToken(context.request, db, requiredPermission);
|
||
if (tokenValidation.valid) {
|
||
// Token验证通过,继续处理请求
|
||
return context.next();
|
||
}
|
||
|
||
// 回退到使用传统身份认证方式
|
||
const { user, pass } = basicAuthentication(context.request);
|
||
if (basicUser !== user || basicPass !== pass) {
|
||
return UnauthorizedException('Invalid credentials.');
|
||
} else {
|
||
return context.next();
|
||
}
|
||
|
||
} else {
|
||
// 要求客户端进行基本认证
|
||
return new Response('You need to login.', {
|
||
status: 401,
|
||
headers: {
|
||
// Prompts the user for credentials.
|
||
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
|
||
// 'WWW-Authenticate': 'None',
|
||
},
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
export const onRequest = [checkDatabaseConfig, errorHandling, authentication]; |