diff --git a/server-patches/patch-email-verification.js b/server-patches/patch-email-verification.js new file mode 100644 index 0000000..bfc1967 --- /dev/null +++ b/server-patches/patch-email-verification.js @@ -0,0 +1,291 @@ +const fs = require("fs"); + +// ── Patch 1: context.js ────────────────────────────────────── +const ctxPath = "/opt/omniai-server/src/routes/context.js"; +let ctx = fs.readFileSync(ctxPath, "utf8"); + +const smsMaxLine = "const SMS_CODE_MAX_ATTEMPTS = Math.max(1, Number(process.env.SMS_CODE_MAX_ATTEMPTS) || 5);"; +const emailConsts = ` +const EMAIL_PURPOSES = new Set(["register", "login", "reset"]); +const EMAIL_CODE_TTL_MINUTES = Math.max(1, Number(process.env.EMAIL_CODE_TTL_MINUTES) || 10); +const EMAIL_CODE_COOLDOWN_SECONDS = Math.max(10, Number(process.env.EMAIL_CODE_COOLDOWN_SECONDS) || 60); +const EMAIL_CODE_MAX_ATTEMPTS = Math.max(1, Number(process.env.EMAIL_CODE_MAX_ATTEMPTS) || 5);`; + +if (!ctx.includes("EMAIL_PURPOSES")) { + ctx = ctx.replace(smsMaxLine, smsMaxLine + emailConsts); + console.log("[ctx] added EMAIL_PURPOSES"); +} + +const afterConsume = ' await pool.query("UPDATE sms_verification_codes SET consumed_at = NOW() WHERE id = $1", [row.id]);\n return true;\n}'; +const emailFuncs = ` +function hashEmailCode(email, code) { + const secret = process.env.EMAIL_CODE_SECRET || process.env.JWT_SECRET || "omniai-dev-email-secret"; + return crypto.createHash("sha256").update(email + ":" + code + ":" + secret).digest("hex"); +} + +async function sendEmailCode(email, code, purpose) { + const provider = String(process.env.EMAIL_PROVIDER || "mock").trim().toLowerCase(); + + if (provider === "smtp") { + const nodemailer = require("nodemailer"); + const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: Number(process.env.SMTP_PORT) || 587, + secure: process.env.SMTP_SECURE === "1", + auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }, + }); + + const purposeText = purpose === "register" ? "\u6ce8\u518c" : purpose === "login" ? "\u767b\u5f55" : "\u91cd\u7f6e\u5bc6\u7801"; + await transporter.sendMail({ + from: process.env.SMTP_FROM || process.env.SMTP_USER, + to: email, + subject: "[OmniAI] \u90ae\u7bb1\u9a8c\u8bc1\u7801", + text: "\u60a8\u7684\u9a8c\u8bc1\u7801\u662f\uff1a" + code + "\n\u7528\u9014\uff1a" + purposeText + "\n\u6709\u6548\u671f\uff1a" + String(process.env.EMAIL_CODE_TTL_MINUTES || 10) + " \u5206\u949f\n\u5982\u679c\u4e0d\u662f\u60a8\u672c\u4eba\u64cd\u4f5c\uff0c\u8bf7\u5ffd\u7565\u6b64\u90ae\u4ef6\u3002", + html: '
\u60a8\u7684\u9a8c\u8bc1\u7801\u662f\uff1a
' + code + '
\u7528\u9014\uff1a' + purposeText + '
\u6709\u6548\u671f\uff1a' + String(process.env.EMAIL_CODE_TTL_MINUTES || 10) + ' \u5206\u949f
\u5982\u679c\u4e0d\u662f\u60a8\u672c\u4eba\u64cd\u4f5c\uff0c\u8bf7\u5ffd\u7565\u6b64\u90ae\u4ef6\u3002
您访问的页面不存在或已被移除。
+ +{selectedPlan.price},{selectedPlan.grant}
+{order.message || "支付完成后积分将自动入账,如长时间未到账请联系客服。"}
+