feat: 添加 API Token 过期时间后端支持

- 新增 tokenExpiration.js 工具模块(过期判定、自动删除过滤)
- apiTokens.js 支持 expiresAt/autoDelete 字段的创建、更新、自动清理
- 新增 getTokenData 导出函数
- tokenValidator.js 增加过期检查逻辑
This commit is contained in:
MarSeventh
2026-03-10 10:52:01 +08:00
parent 2d83c9cd04
commit 2628de051d
4 changed files with 131 additions and 20 deletions
+73 -13
View File
@@ -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
}
+40
View File
@@ -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 };
}
+17 -6
View File
@@ -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
View File
@@ -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\"",