Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 23 additions & 32 deletions backend/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,7 @@ export async function handleUserRegistration(userData, env) {
await env.USERS.put(email, JSON.stringify(user));

// 生成JWT token
const token = await generateJWT(
{ userId: user.id, email: user.email },
env.JWT_SECRET || "your-secret-key",
604800
);
const token = await generateJWT({ userId: user.id, email: user.email }, env.JWT_SECRET, 604800);

// 返回用户信息(不包含敏感数据)
const userResponse = {
Expand Down Expand Up @@ -378,7 +374,7 @@ export async function handleUserLogin(credentials, env) {
// 生成JWT token
const token = await generateJWT(
{ userId: userForPersistence.id, email: userForPersistence.email },
env.JWT_SECRET || "your-secret-key",
env.JWT_SECRET,
604800
);

Expand Down Expand Up @@ -423,14 +419,23 @@ export async function validateUserToken(token, env) {
}

// 标准HS256验证,失败再尝试旧制式;不再允许“仅解析载荷放行”
let claims = await verifyJWT(token, env.JWT_SECRET || "your-secret-key");
if (!env.JWT_SECRET) {
console.error("[Auth Error] JWT_SECRET 未配置");
return {
success: false,
error: "服务器配置错误",
cause: "missing_jwt_secret",
};
}

let claims = await verifyJWT(token, env.JWT_SECRET);
let needIssueNewToken = false;
const allowLegacy =
String(env.JWT_ALLOW_LEGACY === undefined ? "true" : env.JWT_ALLOW_LEGACY).toLowerCase() !==
"false";
if (!claims) {
if (allowLegacy) {
const legacyClaims = legacyVerifyJWT(token, env.JWT_SECRET || "your-secret-key");
const legacyClaims = legacyVerifyJWT(token, env.JWT_SECRET);
if (!legacyClaims) {
return {
success: false,
Expand Down Expand Up @@ -513,7 +518,7 @@ export async function validateUserToken(token, env) {
try {
rotatedToken = await generateJWT(
{ userId: user.id, email: user.email },
env.JWT_SECRET || "your-secret-key",
env.JWT_SECRET,
604800
);
} catch (_) {}
Expand Down Expand Up @@ -576,11 +581,8 @@ export async function authenticateUser(request, env) {
* @param {string} email - 用户邮箱
* @returns {string} 重置token
*/
function generateResetToken(email) {
const timestamp = Date.now();
const randomStr = Math.random().toString(36).substring(2, 15);
const data = `${email}:${timestamp}:${randomStr}`;
return btoa(data).replace(/[+/=]/g, "");
function generateResetToken() {
return randomBytes(32).toString("hex");
}

/**
Expand Down Expand Up @@ -632,8 +634,7 @@ export async function handleForgotPassword(requestData, env) {
}

// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
if (!validateEmail(email)) {
return {
success: false,
error: "邮箱格式不正确",
Expand All @@ -654,7 +655,7 @@ export async function handleForgotPassword(requestData, env) {
const user = JSON.parse(userData);

// 生成重置token
const resetToken = generateResetToken(email);
const resetToken = generateResetToken();

// 存储重置token(可以存储在KV中,设置过期时间)
const resetData = {
Expand All @@ -669,14 +670,12 @@ export async function handleForgotPassword(requestData, env) {
expirationTtl: 24 * 60 * 60, // 24小时
});

// TODO: 这里应该发送邮件给用户
// 目前返回重置链接(生产环境中应该通过邮件发送)
const resetUrl = `${env.FRONTEND_URL || "https://aistone.org"}/reset-password?token=${resetToken}`;
// TODO: 集成邮件服务(Cloudflare Email Workers / SendGrid / Resend)发送重置链接
// const resetUrl = `${env.FRONTEND_URL || "https://aistone.org"}/reset-password?token=${resetToken}`;

return {
success: true,
message: "重置密码链接已发送到您的邮箱",
resetUrl: resetUrl, // 开发环境返回链接,生产环境删除此行
message: "如果该邮箱已注册,您将收到重置密码的链接",
};
} catch (error) {
console.error("忘记密码错误:", error);
Expand Down Expand Up @@ -902,11 +901,7 @@ export async function handleGoogleLogin(requestData, env) {
await env.USERS.put(emailLower, JSON.stringify(user));

// 生成JWT token
const token = await generateJWT(
{ userId: user.id, email: user.email },
env.JWT_SECRET || "your-secret-key",
604800
);
const token = await generateJWT({ userId: user.id, email: user.email }, env.JWT_SECRET, 604800);

// 返回用户信息(不包含敏感数据)
const userResponse = {
Expand Down Expand Up @@ -1095,11 +1090,7 @@ export async function handleGoogleOAuth(requestData, env) {
}

// 生成JWT token
const token = await generateJWT(
{ userId: user.id, email: user.email },
env.JWT_SECRET || "your-secret-key",
604800
);
const token = await generateJWT({ userId: user.id, email: user.email }, env.JWT_SECRET, 604800);

// 返回用户信息(不包含敏感数据)
const userResponse = {
Expand Down
1 change: 0 additions & 1 deletion backend/image_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ class HDImageCacheManager {
* @returns {Promise<Object|null>} 用户信息或null
*/
async function authenticateImageAccess(request, env) {
// 导入认证相关函数
const { extractTokenFromRequest, validateUserToken } = await import("./auth.js");

const token = extractTokenFromRequest(request);
Expand Down
12 changes: 8 additions & 4 deletions backend/routes/feedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ export function registerFeedbackRoutes(registerRoute) {
registerRoute({
method: "GET",
path: "/api/admin/feedback",
async handler({ env, url }) {
const adminKey = url.searchParams.get("admin_key");
if (adminKey !== env.ADMIN_KEY) {
async handler({ request, env, url }) {
const authHeader = request.headers.get("Authorization") || "";
const headerKey = authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
const adminKey = headerKey || url.searchParams.get("admin_key");
if (!adminKey || adminKey !== env.ADMIN_KEY) {
return jsonResponse({ error: "管理员权限验证失败" }, env, 403);
}
const page = parseInt(url.searchParams.get("page") || "1", 10);
const pageSize = parseInt(url.searchParams.get("page_size") || "50", 10);
const t0 = Date.now();
const result = await getAllFeedbackForAdmin(env);
const result = await getAllFeedbackForAdmin(env, { page, pageSize });
const dt = Date.now() - t0;
recordMetric(env, "feedback_admin_list", { success: result.success, dt_ms: dt });
if (!result.success) {
Expand Down
51 changes: 29 additions & 22 deletions backend/services/feedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,25 @@ export async function getUserFeedbackList(user, env) {
}

const feedbackIds = JSON.parse(feedbackListStr);
const feedbacks = [];

for (const feedbackId of feedbackIds) {
try {
const feedbackStr = await env.FEEDBACK.get(feedbackId);
if (feedbackStr) {
const feedback = JSON.parse(feedbackStr);
const results = await Promise.allSettled(feedbackIds.map((id) => env.FEEDBACK.get(id)));

const feedbacks = [];
for (let i = 0; i < results.length; i++) {
const r = results[i];
if (r.status === "fulfilled" && r.value) {
try {
const feedback = JSON.parse(r.value);
feedbacks.push({
id: feedback.id,
category: feedback.category,
content: feedback.content,
created_at: feedback.created_at,
status: feedback.status || "pending",
});
} catch (e) {
console.error(`解析反馈失败: ${feedbackIds[i]}`, e);
}
} catch (e) {
console.error(`获取反馈详情失败: ${feedbackId}`, e);
}
}

Expand All @@ -105,40 +107,45 @@ export async function getUserFeedbackList(user, env) {
}
}

export async function getAllFeedbackForAdmin(env) {
export async function getAllFeedbackForAdmin(env, { page = 1, pageSize = 50 } = {}) {
try {
const allKeys = await env.FEEDBACK.list({ prefix: "feedback_" });
const allFeedback = [];

for (const key of allKeys.keys) {
try {
const feedbackStr = await env.FEEDBACK.get(key.name);
if (feedbackStr) {
const feedback = JSON.parse(feedbackStr);
allFeedback.push(feedback);
}
} catch (e) {
console.error(`获取反馈详情失败: ${key.name}`, e);
const results = await Promise.allSettled(allKeys.keys.map((key) => env.FEEDBACK.get(key.name)));

const allFeedback = [];
for (const r of results) {
if (r.status === "fulfilled" && r.value) {
try {
allFeedback.push(JSON.parse(r.value));
} catch (_) {}
}
}

allFeedback.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));

const todayStr = new Date().toISOString().split("T")[0];
const stats = {
total: allFeedback.length,
pending: allFeedback.filter((f) => f.status === "pending").length,
processed: allFeedback.filter((f) => f.status === "processed").length,
today: allFeedback.filter((f) => {
const today = new Date().toISOString().split("T")[0];
const feedbackDate = new Date(f.created_at).toISOString().split("T")[0];
return feedbackDate === today;
return feedbackDate === todayStr;
}).length,
};

const start = (page - 1) * pageSize;
const paginatedFeedback = allFeedback.slice(start, start + pageSize);

return {
success: true,
feedbacks: allFeedback,
feedbacks: paginatedFeedback,
stats,
count: allFeedback.length,
page,
pageSize,
totalPages: Math.ceil(allFeedback.length / pageSize),
};
} catch (error) {
console.error("管理员获取反馈列表时出错:", error);
Expand Down
Loading
Loading