Browse Source

fix(config): harden comment-email test endpoint safety

Add IP-based rate limiting for the admin test-email API and set SMTP connection/send timeouts to avoid long blocking requests, with extra admin email format validation.

Made-with: Cursor
main
npmrun 3 weeks ago
parent
commit
b1e42902af
  1. 4
      server/api/config/global/comment-email-test.post.ts
  2. 4
      server/service/comment-email/test-mail.test.ts
  3. 7
      server/service/comment-email/test-mail.ts

4
server/api/config/global/comment-email-test.post.ts

@ -2,12 +2,16 @@ import { dbGlobal } from "drizzle-pkg/lib/db";
import { users } from "drizzle-pkg/lib/schema/auth"; import { users } from "drizzle-pkg/lib/schema/auth";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { requireAdmin } from "#server/utils/admin-guard"; import { requireAdmin } from "#server/utils/admin-guard";
import { assertUnderRateLimit } from "#server/utils/simple-rate-limit";
import { getRequestIP } from "h3";
import { import {
CommentEmailTestValidationError, CommentEmailTestValidationError,
sendCommentEmailTestMail, sendCommentEmailTestMail,
} from "#server/service/comment-email/test-mail"; } from "#server/service/comment-email/test-mail";
export default defineWrappedResponseHandler(async (event) => { export default defineWrappedResponseHandler(async (event) => {
const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown";
assertUnderRateLimit(`admin-config-comment-email-test:${ip}`, 5, 60_000);
const admin = await requireAdmin(event); const admin = await requireAdmin(event);
const [adminRow] = await dbGlobal const [adminRow] = await dbGlobal
.select({ .select({

4
server/service/comment-email/test-mail.test.ts

@ -23,4 +23,8 @@ describe("comment email test mail validation", () => {
test("returns 400-equivalent validation error when admin email is empty", () => { test("returns 400-equivalent validation error when admin email is empty", () => {
expect(() => assertAdminEmailReady("")).toThrow(CommentEmailTestValidationError); expect(() => assertAdminEmailReady("")).toThrow(CommentEmailTestValidationError);
}); });
test("returns 400-equivalent validation error when admin email format is invalid", () => {
expect(() => assertAdminEmailReady("not-an-email")).toThrow(CommentEmailTestValidationError);
});
}); });

7
server/service/comment-email/test-mail.ts

@ -12,6 +12,7 @@ type CommentEmailConfig = {
}; };
const logger = log4js.getLogger("COMMENT_EMAIL_TEST"); const logger = log4js.getLogger("COMMENT_EMAIL_TEST");
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export class CommentEmailTestValidationError extends Error { export class CommentEmailTestValidationError extends Error {
constructor(message: string) { constructor(message: string) {
@ -40,6 +41,9 @@ export function assertAdminEmailReady(adminEmail: string | null | undefined): as
if (!adminEmail || adminEmail.trim().length === 0) { if (!adminEmail || adminEmail.trim().length === 0) {
throw new CommentEmailTestValidationError("当前管理员账号未配置邮箱,无法接收测试邮件"); throw new CommentEmailTestValidationError("当前管理员账号未配置邮箱,无法接收测试邮件");
} }
if (!EMAIL_REGEX.test(adminEmail.trim())) {
throw new CommentEmailTestValidationError("当前管理员账号邮箱格式不合法");
}
} }
export async function sendCommentEmailTestMail(input: { export async function sendCommentEmailTestMail(input: {
@ -54,6 +58,9 @@ export async function sendCommentEmailTestMail(input: {
host: input.config.smtpHost, host: input.config.smtpHost,
port: input.config.smtpPort, port: input.config.smtpPort,
secure: input.config.smtpSecure, secure: input.config.smtpSecure,
connectionTimeout: 10_000,
greetingTimeout: 10_000,
socketTimeout: 15_000,
auth: { auth: {
user: input.config.smtpUser, user: input.config.smtpUser,
pass: input.config.smtpPass, pass: input.config.smtpPass,

Loading…
Cancel
Save