diff --git a/packages/openauth/src/provider/password.ts b/packages/openauth/src/provider/password.ts index c4eba4a1..373cc6b6 100644 --- a/packages/openauth/src/provider/password.ts +++ b/packages/openauth/src/provider/password.ts @@ -120,13 +120,17 @@ export interface PasswordConfig { * @example * ```ts * { - * sendCode: async (email, code) => { + * sendCode: async (email, code, type) => { * // Send an email with the code * } * } * ``` */ - sendCode: (email: string, code: string) => Promise + sendCode: ( + email: string, + code: string, + type: "register" | "change", + ) => Promise /** * Callback to validate the password on sign up and password reset. * @@ -378,7 +382,7 @@ export function PasswordProvider( ]) if (existing) return transition(provider, { type: "email_taken" }) const code = generate() - await config.sendCode(email, code) + await config.sendCode(email, code, "register") return transition({ type: "code", code, @@ -389,7 +393,7 @@ export function PasswordProvider( if (action === "register" && provider.type === "code") { const code = generate() - await config.sendCode(provider.email, code) + await config.sendCode(provider.email, code, "register") return transition({ type: "code", code, @@ -455,7 +459,7 @@ export function PasswordProvider( { type: "invalid_email" }, ) const code = generate() - await config.sendCode(email, code) + await config.sendCode(email, code, "change") return transition({ type: "code", diff --git a/packages/openauth/test/password.provider.test.ts b/packages/openauth/test/password.provider.test.ts new file mode 100644 index 00000000..11f79751 --- /dev/null +++ b/packages/openauth/test/password.provider.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, mock, test } from "bun:test" +import { object, string } from "valibot" +import { issuer } from "../src/issuer.js" +import { PasswordProvider } from "../src/provider/password.js" +import { MemoryStorage } from "../src/storage/memory.js" +import { createSubjects } from "../src/subject.js" + +const subjects = createSubjects({ + user: object({ email: string() }), +}) + +function buildApp( + sendCode: ( + email: string, + code: string, + type: "register" | "change", + ) => Promise, +) { + return issuer({ + storage: MemoryStorage(), + subjects, + allow: async () => true, + providers: { + password: PasswordProvider({ + sendCode, + login: async () => new Response("login"), + register: async () => new Response("register"), + change: async () => new Response("change"), + }), + }, + success: async (ctx) => ctx.subject("user", { email: "test@example.com" }), + }) +} + +function getSessionCookie(res: Response): string { + return res.headers.get("set-cookie")?.split(";")[0] ?? "" +} + +describe("PasswordProvider sendCode type prop", () => { + test("sendCode is called with type='register'", async () => { + const sendCode = mock(async () => {}) + const app = buildApp(sendCode) + + const getRes = await app.request( + "https://auth.example.com/password/register", + ) + const cookie = getSessionCookie(getRes) + + const fd = new FormData() + fd.append("action", "register") + fd.append("email", "test@example.com") + fd.append("password", "password123") + fd.append("repeat", "password123") + + await app.request("https://auth.example.com/password/register", { + method: "POST", + headers: { cookie }, + body: fd, + }) + + expect(sendCode).toHaveBeenCalledWith( + "test@example.com", + expect.any(String), + "register", + ) + }) + + test("sendCode is called with type='change'", async () => { + const sendCode = mock(async () => {}) + const app = buildApp(sendCode) + + const getRes = await app.request("https://auth.example.com/password/change") + const cookie = getSessionCookie(getRes) + + const fd = new FormData() + fd.append("action", "code") + fd.append("email", "test@example.com") + + await app.request("https://auth.example.com/password/change", { + method: "POST", + headers: { cookie }, + body: fd, + }) + + expect(sendCode).toHaveBeenCalledWith( + "test@example.com", + expect.any(String), + "change", + ) + }) +})