feat: register moderator role in auth config and add guard

This commit is contained in:
2026-05-16 17:06:24 +08:00
parent 0f018b376e
commit 23bbfc0317
2 changed files with 40 additions and 3 deletions
+34 -3
View File
@@ -1,11 +1,37 @@
import { betterAuth } from 'better-auth' import { betterAuth } from 'better-auth'
import { admin, emailOTP, lastLoginMethod } from 'better-auth/plugins' import { admin, emailOTP, lastLoginMethod } from 'better-auth/plugins'
import { passkey } from '@better-auth/passkey' import { passkey } from '@better-auth/passkey'
import { expo } from '@better-auth/expo'
import { drizzleAdapter } from '@better-auth/drizzle-adapter' import { drizzleAdapter } from '@better-auth/drizzle-adapter'
import { db } from '@/lib/db/index' import { db } from '@/lib/db/index'
import { ac, admin as adminRole, contributor, user } from '@/lib/auth/permissions' import { ac, admin as adminRole, moderator, contributor, user } from '@/lib/auth/permissions'
import { sendOTP, OTP_EXPIRY_SECONDS } from '@/lib/auth/email' import { sendOTP, OTP_EXPIRY_SECONDS } from '@/lib/auth/email'
// ── Origin setup ───────────────────────────────────────
// Web + mobile passkey origins
const webOrigin = process.env.BETTER_AUTH_URL ?? 'https://localhost:3000'
const appScheme = 'deckyvault://'
// Build trusted origins: web URL + app scheme + optional expo dev
const trustedOrigins = [
webOrigin,
appScheme,
]
// Add Expo dev URLs in development
if (process.env.NODE_ENV !== 'production') {
trustedOrigins.push('exp://*')
trustedOrigins.push('exp://192.168.*.*:*')
}
// Passkey origins: web URL + optional Android APK key hash
const passkeyOrigins = [
webOrigin,
...(process.env.ANDROID_APK_KEY_HASH
? [`android:apk-key-hash:${process.env.ANDROID_APK_KEY_HASH}`]
: []),
]
export const auth = betterAuth({ export const auth = betterAuth({
experimental: { joins: true }, experimental: { joins: true },
database: drizzleAdapter(db, { database: drizzleAdapter(db, {
@@ -23,7 +49,10 @@ export const auth = betterAuth({
passkey({ passkey({
rpID: process.env.RP_ID ?? 'localhost', rpID: process.env.RP_ID ?? 'localhost',
rpName: 'DeckyVault', rpName: 'DeckyVault',
origin: process.env.BETTER_AUTH_URL ?? 'https://localhost:3000', origin: passkeyOrigins,
advanced: {
webAuthnChallengeCookie: 'better-auth-passkey',
},
}), }),
lastLoginMethod({ lastLoginMethod({
storeInDatabase: true, storeInDatabase: true,
@@ -32,12 +61,14 @@ export const auth = betterAuth({
ac, ac,
roles: { roles: {
admin: adminRole, admin: adminRole,
moderator,
contributor, contributor,
user, user,
}, },
defaultRole: 'user', defaultRole: 'user',
adminRoles: ['admin'], adminRoles: ['admin'],
}), }),
expo(),
], ],
socialProviders: { socialProviders: {
google: { google: {
@@ -49,7 +80,7 @@ export const auth = betterAuth({
clientSecret: process.env.DISCORD_CLIENT_SECRET || "", clientSecret: process.env.DISCORD_CLIENT_SECRET || "",
}, },
}, },
trustedOrigins: [process.env.NEXT_PUBLIC_SITE_URL || "https://localhost:3000"], trustedOrigins,
rateLimit: { rateLimit: {
enabled: true, enabled: true,
window: 60, window: 60,
+6
View File
@@ -53,3 +53,9 @@ export async function requireContributorOrAdmin(
): Promise<GuardResult> { ): Promise<GuardResult> {
return requireRole(headers, ["contributor", "admin"]) return requireRole(headers, ["contributor", "admin"])
} }
export async function requireModeratorOrAdmin(
headers: Headers,
): Promise<GuardResult> {
return requireRole(headers, ["moderator", "admin"])
}