feat : more clear no reply mail

This commit is contained in:
2026-10-07 17:14:54 +07:00 Unverified
parent 5d195968ec
commit 012b1e11b1
3 changed files with 62 additions and 8 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ export function EmailActionForm({ mode, token = "", invalidToken = false, nextPa
<Input id="confirm-password" name="confirmPassword" type="password" minLength={6} maxLength={128} <Input id="confirm-password" name="confirmPassword" type="password" minLength={6} maxLength={128}
autoComplete="new-password" required disabled={busy} /></Field> autoComplete="new-password" required disabled={busy} /></Field>
</>} </>}
<Button type="submit" className="min-h-11" disabled={busy}>{busy ? "กำลังดำเนินการ..." : <Button type="submit" disabled={busy}>{busy ? "กำลังดำเนินการ..." :
mode === "forgot" ? "ส่งลิงก์รีเซ็ต" : "ตั้งรหัสผ่าน"}</Button> mode === "forgot" ? "ส่งลิงก์รีเซ็ต" : "ตั้งรหัสผ่าน"}</Button>
</FieldGroup> </FieldGroup>
</form>} </form>}
+22 -2
View File
@@ -81,19 +81,39 @@ describe("actual administrator session boundary", () => {
(await import("./server")).getAuth(); (await import("./server")).getAuth();
const options = mocks.auth.mock.calls.at(-1)![0]; const options = mocks.auth.mock.calls.at(-1)![0];
expect(options.emailAndPassword.requireEmailVerification).toBe(true); expect(options.emailAndPassword.requireEmailVerification).toBe(true);
expect(options.emailAndPassword.resetPasswordTokenExpiresIn).toBe(3600);
expect(options.emailVerification).toMatchObject({ expect(options.emailVerification).toMatchObject({
sendOnSignUp: false, autoSignInAfterVerification: true, sendOnSignUp: false, autoSignInAfterVerification: true, expiresIn: 3600,
}); });
expect(options.user.changeEmail.enabled).toBe(true); expect(options.user.changeEmail.enabled).toBe(true);
const otp = options.plugins.find((plugin: { id?: string }) => plugin.id === "email-otp"); const otp = options.plugins.find((plugin: { id?: string }) => plugin.id === "email-otp");
expect(otp.options).toMatchObject({ sendVerificationOnSignUp: true, storeOTP: "hashed" }); expect(otp.options).toMatchObject({ sendVerificationOnSignUp: true, storeOTP: "hashed", expiresIn: 300 });
const user = { email: "[email protected]" }; const user = { email: "[email protected]" };
await otp.options.sendVerificationOTP({ email: user.email, otp: "123456", type: "email-verification" }); await otp.options.sendVerificationOTP({ email: user.email, otp: "123456", type: "email-verification" });
const otpEmail = JSON.parse(String(fetchMock.mock.calls[0][1]?.body));
expect(otpEmail.subject).toContain("รหัส OTP");
expect(otpEmail.text).toContain("รหัส OTP: 123456");
expect(otpEmail.text).toContain("หมดอายุใน 5 นาที");
expect(otpEmail.text).toContain("อย่าแชร์รหัสนี้กับผู้อื่น");
expect(otpEmail.text).toContain("กรุณาอย่าตอบกลับ");
await options.emailVerification.sendVerificationEmail({ user, url: "https://guide.example.test/verify-email?token=abc" }); await options.emailVerification.sendVerificationEmail({ user, url: "https://guide.example.test/verify-email?token=abc" });
await options.user.changeEmail.sendChangeEmailConfirmation({ user, newEmail: "[email protected]", await options.user.changeEmail.sendChangeEmailConfirmation({ user, newEmail: "[email protected]",
url: "https://guide.example.test/verify-email?token=change" }); url: "https://guide.example.test/verify-email?token=change" });
await options.emailAndPassword.sendResetPassword({ user, url: "https://guide.example.test/reset-password/abc" }); await options.emailAndPassword.sendResetPassword({ user, url: "https://guide.example.test/reset-password/abc" });
expect(fetchMock).toHaveBeenCalledTimes(4); expect(fetchMock).toHaveBeenCalledTimes(4);
const emails = fetchMock.mock.calls.map((call) => JSON.parse(String(call[1]?.body)));
expect(emails[1].text).toContain(user.email);
expect(emails[1].text).toContain("ยืนยันอีเมลใหม่");
expect(emails[2].text).toContain(`อีเมลปัจจุบัน: ${user.email}\nอีเมลใหม่: [email protected]`);
expect(emails[2].text).toContain("คุณต้องเปิดลิงก์ในอีเมลใหม่");
expect(emails[3].text).toContain("รหัสผ่านของคุณจะเปลี่ยนเมื่อ");
expect(emails[3].text).toContain("รหัสผ่านเดิมของคุณจะยังใช้งานได้");
for (const email of emails.slice(1)) {
expect(email.text).toContain("หมดอายุใน 1 ชั่วโมง");
expect(email.text).toContain("อย่าแชร์หรือส่งต่อลิงก์นี้");
expect(email.text).toContain("หากคุณไม่ได้");
expect(email.text).toContain("กรุณาอย่าตอบกลับ");
}
for (const call of fetchMock.mock.calls) { for (const call of fetchMock.mock.calls) {
expect(call[0]).toBe("https://api.resend.com/emails"); expect(call[0]).toBe("https://api.resend.com/emails");
expect(call[1]?.headers).toMatchObject({ Authorization: "Bearer test-key" }); expect(call[1]?.headers).toMatchObject({ Authorization: "Bearer test-key" });
+39 -5
View File
@@ -56,24 +56,50 @@ function createAuth() {
requireEmailVerification: true, requireEmailVerification: true,
minPasswordLength: 6, minPasswordLength: 6,
maxPasswordLength: 128, maxPasswordLength: 128,
resetPasswordTokenExpiresIn: 3600,
sendResetPassword: async ({ user, url }) => { sendResetPassword: async ({ user, url }) => {
await sendAuthEmail(user.email, "รีเซ็ตรหัสผ่าน Buzz Guide", `เปิดลิงก์นี้เพื่อรีเซ็ตรหัสผ่าน: ${url}\n\nลิงก์นี้หมดอายุใน 1 ชั่วโมง`); await sendAuthEmail(user.email, "ตั้งรหัสผ่านใหม่สำหรับบัญชี Buzz Guide", [
`เราได้รับคำขอตั้งรหัสผ่านใหม่สำหรับบัญชีที่ใช้อีเมล ${user.email}`,
`เปิดลิงก์นี้เพื่อตั้งรหัสผ่านใหม่:\n${url}`,
"ลิงก์นี้หมดอายุใน 1 ชั่วโมง หากหมดอายุ ให้ขอลิงก์ใหม่จากหน้า “ลืมรหัสผ่าน”",
"รหัสผ่านของคุณจะเปลี่ยนเมื่อคุณตั้งรหัสผ่านใหม่และกดยืนยันบนเว็บไซต์เท่านั้น",
"อย่าแชร์หรือส่งต่อลิงก์นี้ให้ผู้อื่น ทีมงาน Buzz Guide จะไม่ขอรหัสผ่านของคุณ",
"หากคุณไม่ได้ขอเปลี่ยนรหัสผ่าน ไม่ต้องเปิดลิงก์นี้ รหัสผ่านเดิมของคุณจะยังใช้งานได้",
"อีเมลนี้ส่งอัตโนมัติจาก [email protected] กรุณาอย่าตอบกลับ",
].join("\n\n"));
}, },
}, },
user: { user: {
changeEmail: { changeEmail: {
enabled: true, enabled: true,
sendChangeEmailConfirmation: async ({ user, newEmail, url }) => { sendChangeEmailConfirmation: async ({ user, newEmail, url }) => {
await sendAuthEmail(user.email, "ยืนยันการเปลี่ยนอีเมล Buzz Guide", await sendAuthEmail(user.email, "อนุมัติคำขอเปลี่ยนอีเมลบัญชี Buzz Guide", [
`เปิดลิงก์นี้เพื่ออนุมัติการเปลี่ยนอีเมลเป็น ${newEmail}: ${url}\n\nลิงก์นี้หมดอายุใน 1 ชั่วโมง`); "เราได้รับคำขอเปลี่ยนอีเมลสำหรับบัญชี Buzz Guide ของคุณ",
`อีเมลปัจจุบัน: ${user.email}\nอีเมลใหม่: ${newEmail}`,
`หากคุณเป็นผู้ขอเปลี่ยน เปิดลิงก์นี้เพื่ออนุมัติ:\n${url}`,
`หลังอนุมัติ เราจะส่งลิงก์ยืนยันไปที่ ${newEmail} คุณต้องเปิดลิงก์ในอีเมลใหม่เพื่อเปลี่ยนอีเมลให้เสร็จสมบูรณ์`,
"ลิงก์อนุมัตินี้หมดอายุใน 1 ชั่วโมง หากหมดอายุ ให้ขอเปลี่ยนอีเมลอีกครั้งจากหน้าโปรไฟล์",
"อย่าแชร์หรือส่งต่อลิงก์นี้ให้ผู้อื่น",
"หากคุณไม่ได้ขอเปลี่ยนอีเมล อย่าเปิดลิงก์นี้ และตรวจสอบความปลอดภัยของบัญชีจากหน้าโปรไฟล์",
"อีเมลนี้ส่งอัตโนมัติจาก [email protected] กรุณาอย่าตอบกลับ",
].join("\n\n"));
}, },
}, },
}, },
emailVerification: { emailVerification: {
sendOnSignUp: false, sendOnSignUp: false,
autoSignInAfterVerification: true, autoSignInAfterVerification: true,
expiresIn: 3600,
sendVerificationEmail: async ({ user, url }) => { sendVerificationEmail: async ({ user, url }) => {
await sendAuthEmail(user.email, "ยืนยันอีเมล Buzz Guide", `เปิดลิงก์นี้เพื่อยืนยันอีเมล: ${url}\n\nลิงก์นี้หมดอายุใน 1 ชั่วโมง`); await sendAuthEmail(user.email, "ยืนยันอีเมลสำหรับบัญชี Buzz Guide", [
`ยืนยันว่า ${user.email} เป็นอีเมลที่คุณต้องการใช้กับบัญชี Buzz Guide`,
`เปิดลิงก์นี้เพื่อยืนยันอีเมล:\n${url}`,
"หากคุณกำลังเปลี่ยนอีเมลบัญชี การเปิดลิงก์นี้จะยืนยันอีเมลใหม่และทำให้การเปลี่ยนอีเมลเสร็จสมบูรณ์",
"ลิงก์นี้หมดอายุใน 1 ชั่วโมง หากหมดอายุ ให้ขอยืนยันอีเมลอีกครั้งบนเว็บไซต์",
"อย่าแชร์หรือส่งต่อลิงก์นี้ให้ผู้อื่น",
"หากคุณไม่ได้สมัครบัญชีหรือขอเปลี่ยนอีเมล อย่าเปิดลิงก์นี้ และไม่ต้องดำเนินการใด ๆ",
"อีเมลนี้ส่งอัตโนมัติจาก [email protected] กรุณาอย่าตอบกลับ",
].join("\n\n"));
}, },
}, },
advanced: { advanced: {
@@ -97,8 +123,16 @@ function createAuth() {
emailOTP({ emailOTP({
sendVerificationOnSignUp: true, sendVerificationOnSignUp: true,
storeOTP: "hashed", storeOTP: "hashed",
expiresIn: 300,
sendVerificationOTP: async ({ email, otp }) => { sendVerificationOTP: async ({ email, otp }) => {
await sendAuthEmail(email, "รหัสยืนยัน Buzz Guide", `รหัสยืนยันอีเมลของคุณคือ ${otp}\n\nรหัสนี้หมดอายุใน 5 นาที`); await sendAuthEmail(email, "รหัส OTP สำหรับยืนยันอีเมล Buzz Guide", [
"ใช้รหัสนี้เพื่อยืนยันอีเมลของคุณใน Buzz Guide",
`รหัส OTP: ${otp}`,
"รหัสนี้หมดอายุใน 5 นาที หากหมดอายุ ให้กดส่งรหัสใหม่บนหน้ายืนยันอีเมล",
"อย่าแชร์รหัสนี้กับผู้อื่น ทีมงาน Buzz Guide จะไม่ขอรหัส OTP ของคุณ",
"หากคุณไม่ได้ขอรหัสนี้ ไม่ต้องดำเนินการใด ๆ และอย่าส่งรหัสให้ใคร",
"อีเมลนี้ส่งอัตโนมัติจาก [email protected] กรุณาอย่าตอบกลับ",
].join("\n\n"));
}, },
}), }),
...(process.env.NODE_ENV === "development" ? [] : [ ...(process.env.NODE_ENV === "development" ? [] : [