Add user management

This commit is contained in:
2026-01-01 18:34:02 +01:00
parent 2fcf19c0df
commit 36fb2358dd
26 changed files with 1047 additions and 56 deletions

View File

@ -1,3 +1,7 @@
import { createAuthClient } from "better-auth/client";
import type { auth } from "@/lib/auth";
import { inferAdditionalFields } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient();
export const authClient = createAuthClient({
plugins: [inferAdditionalFields<typeof auth>()],
});

View File

@ -1,12 +1,53 @@
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { admin } from "better-auth/plugins";
import { sendEmail } from "./email";
import { prisma } from "./prisma";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
emailAndPassword: {
enabled: true,
},
user: {
additionalFields: {
role: {
type: ["user", "admin"],
required: false,
defaultValue: "user",
input: false,
},
},
},
emailVerification: {
sendOnSignUp: true,
sendOnSignIn: true,
autoSignInAfterVerification: true,
sendVerificationEmail: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Verify your email",
text: `Please verify your email by opening this link:\n\n${url}\n`,
});
},
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendResetPassword: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Reset your password",
text: `Reset your password using this link:\n\n${url}\n`,
});
},
},
plugins: [
admin(),
nextCookies(),
],
});

48
src/lib/email.ts Normal file
View File

@ -0,0 +1,48 @@
import nodemailer from "nodemailer";
type SendEmailArgs = {
to: string;
subject: string;
text: string;
html?: string;
};
let cached: nodemailer.Transporter | null = null;
function getTransporter() {
if (cached) return cached;
const host = process.env.SMTP_HOST;
const port = Number(process.env.SMTP_PORT ?? "587");
const secure = String(process.env.SMTP_SECURE ?? "false") === "true";
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_PASS;
if (!host || !user || !pass) {
throw new Error("SMTP env vars missing (SMTP_HOST/SMTP_USER/SMTP_PASS).");
}
cached = nodemailer.createTransport({
host,
port,
secure, // false for STARTTLS (587), true for 465
auth: { user, pass },
});
return cached;
}
export async function sendEmail(args: SendEmailArgs) {
const from = process.env.SMTP_FROM || process.env.SMTP_USER;
if (!from) throw new Error("SMTP_FROM (or SMTP_USER) must be set.");
const transporter = getTransporter();
await transporter.sendMail({
from,
to: args.to,
subject: args.subject,
text: args.text,
html: args.html,
});
}

6
src/lib/registration.ts Normal file
View File

@ -0,0 +1,6 @@
import { prisma } from "@/lib/prisma";
export async function isPublicRegistrationOpen() {
const count = await prisma.user.count();
return count === 0;
}