Files
pfadi-bussle/lib/auth.ts
Thomas Ruoff d63ded8c6a migrate auth from next-auth to better-auth with magic link support
Replace next-auth with better-auth, adding magic link email login as
the primary auth method and GitHub OAuth as an optional social provider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:19:37 +01:00

92 lines
2.4 KiB
TypeScript

import { betterAuth } from "better-auth"
import { magicLink } from "better-auth/plugins"
import { mongodbAdapter } from "better-auth/adapters/mongodb"
import { MongoClient, ServerApiVersion } from "mongodb"
import { MONGO_URI } from "../db"
import nodemailer from "nodemailer"
async function sendEmail({ to, subject, url }: { to: string; subject: string; url: string }) {
const transporter = nodemailer.createTransport({
host: "wirtanen.uberspace.de",
port: 465,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
await transporter.sendMail({
from: process.env.FROM_EMAIL,
to,
subject,
text: url,
html: `<p>Click <a href="${url}">here</a> to sign in.</p>`,
})
}
const ADMIN_EMAIL = process.env.ADMIN_EMAIL
const GITHUB_USERS_GRANTED = ['111471']
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID;
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
const GITHUB_ENABLED = Boolean(GITHUB_CLIENT_SECRET?.length && GITHUB_CLIENT_ID?.length);
const client = new MongoClient(MONGO_URI, {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
}
})
export const auth = betterAuth({
database: mongodbAdapter(client.db()),
plugins: [
magicLink({
sendMagicLink: async ({ email, url }) => {
await sendEmail({ to: email, subject: "Sign in to Pfadi Bussle", url })
},
}),
],
...(GITHUB_ENABLED ? {
socialProviders: {
github: {
provider: 'github',
clientId: GITHUB_CLIENT_ID,
clientSecret: GITHUB_CLIENT_SECRET,
},
},
} : {}),
account: {
accountLinking: {
enabled: true,
trustedProviders: ["github"],
},
},
user: {
additionalFields: {
role: {
type: "string",
defaultValue: "user",
},
},
},
onRequest: async (request) => {
// Authorization logic
const session = request.context?.session
if (session?.user) {
const account = request.context?.account
// GitHub provider check
if (account?.providerId === 'github') {
if (!GITHUB_USERS_GRANTED.includes(account.providerAccountId)) {
throw new Error('Unauthorized GitHub user')
}
}
// Email check - only allow admin email
if (session.user.email && session.user.email !== ADMIN_EMAIL) {
throw new Error('Unauthorized email')
}
}
},
})