mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-13 04:03:42 +08:00
feat: 添加 API Token 过期时间后端支持
- 新增 tokenExpiration.js 工具模块(过期判定、自动删除过滤) - apiTokens.js 支持 expiresAt/autoDelete 字段的创建、更新、自动清理 - 新增 getTokenData 导出函数 - tokenValidator.js 增加过期检查逻辑
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { getDatabase } from '../../utils/databaseAdapter.js';
|
||||
import { filterAutoDeleteTokens } from '../../utils/tokenExpiration.js';
|
||||
|
||||
export async function onRequest(context) {
|
||||
// API Token管理,支持创建、删除、列出Token
|
||||
@@ -24,7 +25,7 @@ export async function onRequest(context) {
|
||||
// POST - 创建新Token
|
||||
if (method === 'POST') {
|
||||
const body = await request.json()
|
||||
const { name, permissions, owner } = body
|
||||
const { name, permissions, owner, expiresAt = null, autoDelete = false } = body
|
||||
|
||||
if (!name || !permissions || !owner) {
|
||||
return new Response(JSON.stringify({ error: '缺少必要参数' }), {
|
||||
@@ -35,7 +36,7 @@ export async function onRequest(context) {
|
||||
})
|
||||
}
|
||||
|
||||
const token = await createApiToken(db, name, permissions, owner)
|
||||
const token = await createApiToken(db, name, permissions, owner, expiresAt, autoDelete)
|
||||
return new Response(JSON.stringify(token), {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
@@ -67,7 +68,7 @@ export async function onRequest(context) {
|
||||
// PUT - 更新Token权限
|
||||
if (method === 'PUT') {
|
||||
const body = await request.json()
|
||||
const { tokenId, permissions } = body
|
||||
const { tokenId, permissions, expiresAt = null, autoDelete = false } = body
|
||||
|
||||
if (!tokenId || !permissions) {
|
||||
return new Response(JSON.stringify({ error: '缺少必要参数' }), {
|
||||
@@ -78,7 +79,7 @@ export async function onRequest(context) {
|
||||
})
|
||||
}
|
||||
|
||||
const result = await updateApiToken(db, tokenId, permissions)
|
||||
const result = await updateApiToken(db, tokenId, permissions, expiresAt, autoDelete)
|
||||
return new Response(JSON.stringify(result), {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
@@ -95,8 +96,8 @@ async function getApiTokens(db) {
|
||||
const settings = settingsStr ? JSON.parse(settingsStr) : {}
|
||||
const tokens = settings.apiTokens?.tokens || {}
|
||||
|
||||
// 返回时不包含实际token值,只返回基本信息
|
||||
const tokenList = Object.keys(tokens).map(id => {
|
||||
// 将 tokens 对象转为数组,并应用向后兼容默认值
|
||||
const tokenArray = Object.keys(tokens).map(id => {
|
||||
const token = tokens[id]
|
||||
return {
|
||||
id,
|
||||
@@ -105,15 +106,41 @@ async function getApiTokens(db) {
|
||||
permissions: token.permissions,
|
||||
createdAt: token.createdAt,
|
||||
updatedAt: token.updatedAt,
|
||||
token: token.token.substr(0, 15) + '...' // 只显示前15位
|
||||
token: token.token,
|
||||
expiresAt: token.expiresAt ?? null,
|
||||
autoDelete: token.autoDelete ?? false
|
||||
}
|
||||
})
|
||||
|
||||
// 使用 filterAutoDeleteTokens 识别需要自动删除的 Token
|
||||
const { toDelete, toKeep } = filterAutoDeleteTokens(tokenArray)
|
||||
|
||||
// 从数据库中删除符合自动删除条件的 Token
|
||||
if (toDelete.length > 0) {
|
||||
for (const t of toDelete) {
|
||||
delete settings.apiTokens.tokens[t.id]
|
||||
}
|
||||
await db.put('manage@sysConfig@security', JSON.stringify(settings))
|
||||
}
|
||||
|
||||
// 返回时不包含实际token值,只返回基本信息
|
||||
const tokenList = toKeep.map(t => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
owner: t.owner,
|
||||
permissions: t.permissions,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
token: t.token.substr(0, 15) + '...', // 只显示前15位
|
||||
expiresAt: t.expiresAt,
|
||||
autoDelete: t.autoDelete
|
||||
}))
|
||||
|
||||
return { tokens: tokenList }
|
||||
}
|
||||
|
||||
// 创建新的API Token
|
||||
async function createApiToken(db, name, permissions, owner) {
|
||||
async function createApiToken(db, name, permissions, owner, expiresAt = null, autoDelete = false) {
|
||||
const settingsStr = await db.get('manage@sysConfig@security')
|
||||
const settings = settingsStr ? JSON.parse(settingsStr) : {}
|
||||
|
||||
@@ -132,7 +159,9 @@ async function createApiToken(db, name, permissions, owner) {
|
||||
owner,
|
||||
permissions,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
expiresAt: expiresAt ?? null,
|
||||
autoDelete: autoDelete === true
|
||||
}
|
||||
|
||||
settings.apiTokens.tokens[tokenId] = tokenData
|
||||
@@ -147,7 +176,9 @@ async function createApiToken(db, name, permissions, owner) {
|
||||
owner,
|
||||
permissions,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
expiresAt: tokenData.expiresAt,
|
||||
autoDelete: tokenData.autoDelete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +199,8 @@ async function deleteApiToken(db, tokenId) {
|
||||
return { success: true, message: 'Token 已删除' }
|
||||
}
|
||||
|
||||
// 更新API Token权限
|
||||
async function updateApiToken(db, tokenId, permissions) {
|
||||
// 更新API Token
|
||||
async function updateApiToken(db, tokenId, permissions, expiresAt = null, autoDelete = false) {
|
||||
const settingsStr = await db.get('manage@sysConfig@security')
|
||||
const settings = settingsStr ? JSON.parse(settingsStr) : {}
|
||||
|
||||
@@ -179,13 +210,15 @@ async function updateApiToken(db, tokenId, permissions) {
|
||||
|
||||
settings.apiTokens.tokens[tokenId].permissions = permissions
|
||||
settings.apiTokens.tokens[tokenId].updatedAt = new Date().toISOString()
|
||||
settings.apiTokens.tokens[tokenId].expiresAt = expiresAt ?? null
|
||||
settings.apiTokens.tokens[tokenId].autoDelete = autoDelete === true
|
||||
|
||||
// 保存到数据库
|
||||
await db.put('manage@sysConfig@security', JSON.stringify(settings))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Token 权限已更新',
|
||||
message: 'Token 已更新',
|
||||
token: settings.apiTokens.tokens[tokenId]
|
||||
}
|
||||
}
|
||||
@@ -220,3 +253,30 @@ export async function getTokenPermissions(db, token) {
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 根据Token获取完整数据对象(供tokenValidator使用)
|
||||
export async function getTokenData(db, token) {
|
||||
const settingsStr = await db.get('manage@sysConfig@security')
|
||||
const settings = settingsStr ? JSON.parse(settingsStr) : {}
|
||||
const tokens = settings.apiTokens?.tokens || {}
|
||||
|
||||
// 查找匹配的token
|
||||
for (const tokenId in tokens) {
|
||||
if (tokens[tokenId].token === token) {
|
||||
const t = tokens[tokenId]
|
||||
return {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
token: t.token,
|
||||
owner: t.owner,
|
||||
permissions: t.permissions,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
expiresAt: t.expiresAt ?? null,
|
||||
autoDelete: t.autoDelete ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Token 过期时间工具模块(后端)
|
||||
* 提供 Token 过期判定和自动删除过滤功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 判定 Token 是否已过期
|
||||
* @param {string|null} expiresAt - 过期时间 ISO 8601 字符串,null 表示永不过期
|
||||
* @param {Date} [now=new Date()] - 当前时间
|
||||
* @returns {boolean} 是否已过期
|
||||
*/
|
||||
export function isExpired(expiresAt, now = new Date()) {
|
||||
if (expiresAt === null || expiresAt === undefined) {
|
||||
return false;
|
||||
}
|
||||
const expiresDate = new Date(expiresAt);
|
||||
const currentTime = now instanceof Date ? now : new Date(now);
|
||||
return currentTime.getTime() > expiresDate.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤出需要自动删除的 Token 并返回保留的 Token 列表
|
||||
* @param {Array<Object>} tokens - Token 数组
|
||||
* @param {Date} [now=new Date()] - 当前时间
|
||||
* @returns {{ toDelete: Array<Object>, toKeep: Array<Object> }}
|
||||
*/
|
||||
export function filterAutoDeleteTokens(tokens, now = new Date()) {
|
||||
const toDelete = [];
|
||||
const toKeep = [];
|
||||
|
||||
for (const token of tokens) {
|
||||
if (isExpired(token.expiresAt, now) && token.autoDelete === true) {
|
||||
toDelete.push(token);
|
||||
} else {
|
||||
toKeep.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
return { toDelete, toKeep };
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// API Token权限验证工具函数
|
||||
import { getTokenPermissions } from '../api/manage/apiTokens.js';
|
||||
import { getTokenData } from '../api/manage/apiTokens.js';
|
||||
import { isExpired } from './tokenExpiration.js';
|
||||
|
||||
/**
|
||||
* 验证API Token权限
|
||||
@@ -28,15 +29,20 @@ export async function validateApiToken(request, db, requiredPermission) {
|
||||
return { valid: false, error: '无效的Token格式' };
|
||||
}
|
||||
|
||||
// 获取Token权限
|
||||
const permissions = await getTokenPermissions(db, token);
|
||||
// 获取完整Token数据
|
||||
const tokenData = await getTokenData(db, token);
|
||||
|
||||
if (!permissions) {
|
||||
if (!tokenData) {
|
||||
return { valid: false, error: '无效的Token' };
|
||||
}
|
||||
|
||||
// 检查Token是否已过期
|
||||
if (isExpired(tokenData.expiresAt)) {
|
||||
return { valid: false, error: 'Token 已过期' };
|
||||
}
|
||||
|
||||
// 检查权限,如果不需要特定权限(requiredPermission为null),则只要token有效就通过
|
||||
if (requiredPermission !== null && !permissions.includes(requiredPermission)) {
|
||||
if (requiredPermission !== null && !tokenData.permissions.includes(requiredPermission)) {
|
||||
return { valid: false, error: `缺少${requiredPermission}权限` };
|
||||
}
|
||||
|
||||
@@ -76,7 +82,12 @@ export async function getTokenInfo(request, kv) {
|
||||
// 查找匹配的token
|
||||
for (const tokenId in tokens) {
|
||||
if (tokens[tokenId].token === token) {
|
||||
return tokens[tokenId];
|
||||
const t = tokens[tokenId];
|
||||
return {
|
||||
...t,
|
||||
expiresAt: t.expiresAt ?? null,
|
||||
autoDelete: t.autoDelete ?? false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloudflare-imgbed",
|
||||
"version": "2.6.2",
|
||||
"version": "2.6.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"ci-test": "concurrently --kill-others \"npm start\" \"wait-on http://localhost:8080 && mocha\"",
|
||||
|
||||
Reference in New Issue
Block a user