diff --git a/src/auth.ts b/src/auth.ts index e02896b..d834242 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,5 +1,8 @@ import { betterAuth } from "better-auth"; +import { buildAuthEmail, sendEmail } from "./email"; import { trustedOrigins, type Env } from "./env"; +import { passwordPolicy } from "./password"; +import { createAuthPlugins } from "./plugins"; export function createAuth(env: Env) { return betterAuth({ @@ -9,6 +12,21 @@ export function createAuth(env: Env) { trustedOrigins: trustedOrigins(env), emailAndPassword: { enabled: true, + ...passwordPolicy, + requireEmailVerification: true, + revokeSessionsOnPasswordReset: true, + sendResetPassword: async ({ user, url }) => { + await sendEmail(env, buildAuthEmail({ kind: "reset-password", to: user.email, url })); + }, }, + emailVerification: { + sendOnSignUp: true, + sendOnSignIn: true, + autoSignInAfterVerification: true, + sendVerificationEmail: async ({ user, url }) => { + await sendEmail(env, buildAuthEmail({ kind: "verify-email", to: user.email, url })); + }, + }, + plugins: createAuthPlugins(env), }); } diff --git a/src/password.ts b/src/password.ts new file mode 100644 index 0000000..a13c8c8 --- /dev/null +++ b/src/password.ts @@ -0,0 +1,4 @@ +export const passwordPolicy = { + minPasswordLength: 12, + maxPasswordLength: 128, +}; diff --git a/src/plugins.ts b/src/plugins.ts new file mode 100644 index 0000000..344b755 --- /dev/null +++ b/src/plugins.ts @@ -0,0 +1,12 @@ +import type { BetterAuthPlugin } from "better-auth"; +import { haveIBeenPwned, openAPI } from "better-auth/plugins"; +import type { Env } from "./env"; + +export function createAuthPlugins(_env: Env): BetterAuthPlugin[] { + return [ + openAPI(), + haveIBeenPwned({ + customPasswordCompromisedMessage: "This password has appeared in a data breach.", + }), + ]; +} diff --git a/tests/auth-worker.test.ts b/tests/auth-worker.test.ts index 51a0c64..6730e16 100644 --- a/tests/auth-worker.test.ts +++ b/tests/auth-worker.test.ts @@ -18,9 +18,10 @@ describe("cfw-auth worker", () => { expect(response.status).toBe(404); }); - it("routes Better Auth traffic through /api/auth/*", async () => { + it("exposes Better Auth OpenAPI reference", async () => { const response = await worker.fetch(new Request("http://auth.local/api/auth/reference"), env); - expect([200, 404, 500]).toContain(response.status); + expect(response.status).toBe(200); + expect(response.headers.get("content-type") ?? "").toContain("text/html"); }); it("applies configured CORS origin for auth routes", async () => {