From 23bbfc03171bb17d23d0f3eb3020a20a23485052 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sat, 16 May 2026 17:06:24 +0800 Subject: [PATCH] feat: register moderator role in auth config and add guard --- lib/auth.ts | 37 ++++++++++++++++++++++++++++++++++--- lib/auth/guard.ts | 6 ++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/auth.ts b/lib/auth.ts index 5208f4c..4e2635c 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,11 +1,37 @@ import { betterAuth } from 'better-auth' import { admin, emailOTP, lastLoginMethod } from 'better-auth/plugins' import { passkey } from '@better-auth/passkey' +import { expo } from '@better-auth/expo' import { drizzleAdapter } from '@better-auth/drizzle-adapter' 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' +// ── 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({ experimental: { joins: true }, database: drizzleAdapter(db, { @@ -23,7 +49,10 @@ export const auth = betterAuth({ passkey({ rpID: process.env.RP_ID ?? 'localhost', rpName: 'DeckyVault', - origin: process.env.BETTER_AUTH_URL ?? 'https://localhost:3000', + origin: passkeyOrigins, + advanced: { + webAuthnChallengeCookie: 'better-auth-passkey', + }, }), lastLoginMethod({ storeInDatabase: true, @@ -32,12 +61,14 @@ export const auth = betterAuth({ ac, roles: { admin: adminRole, + moderator, contributor, user, }, defaultRole: 'user', adminRoles: ['admin'], }), + expo(), ], socialProviders: { google: { @@ -49,7 +80,7 @@ export const auth = betterAuth({ clientSecret: process.env.DISCORD_CLIENT_SECRET || "", }, }, - trustedOrigins: [process.env.NEXT_PUBLIC_SITE_URL || "https://localhost:3000"], + trustedOrigins, rateLimit: { enabled: true, window: 60, diff --git a/lib/auth/guard.ts b/lib/auth/guard.ts index 011c0d1..cdc409a 100644 --- a/lib/auth/guard.ts +++ b/lib/auth/guard.ts @@ -53,3 +53,9 @@ export async function requireContributorOrAdmin( ): Promise { return requireRole(headers, ["contributor", "admin"]) } + +export async function requireModeratorOrAdmin( + headers: Headers, +): Promise { + return requireRole(headers, ["moderator", "admin"]) +}