diff --git a/src/app/lib/auth.ts b/src/app/lib/auth.ts index b8d5b72..6628664 100644 --- a/src/app/lib/auth.ts +++ b/src/app/lib/auth.ts @@ -2,6 +2,16 @@ import { verifyUserRole } from "@/lib/discord"; import type { NextAuthOptions } from "next-auth"; import DiscordProvider, { type DiscordProfile } from "next-auth/providers/discord"; +const ROLE_RECHECK_INTERVAL = 60 * 60 * 1000; + +const roleChecks = new Map(); + +async function checkRole(userId: string): Promise { + const hasRole = await verifyUserRole(userId); + roleChecks.set(userId, { hasRole, checkedAt: Date.now() }); + return hasRole; +} + export const authOptions: NextAuthOptions = { providers: [ DiscordProvider({ @@ -26,13 +36,33 @@ export const authOptions: NextAuthOptions = { ], callbacks: { async signIn({ user }) { - const hasRequiredRole = await verifyUserRole(user.id); - - if (!hasRequiredRole) { + try { + return await checkRole(user.id); + } catch (error) { + console.error("Error verifying user role:", error); return false; } + }, + async jwt({ token }) { + if (!token.sub) { + return token; + } - return true; + const cached = roleChecks.get(token.sub); + let hasRole = cached?.hasRole ?? true; + + if (!cached || Date.now() - cached.checkedAt > ROLE_RECHECK_INTERVAL) { + try { + hasRole = await checkRole(token.sub); + } catch (error) { + console.error("Error re-verifying user role:", error); + } + } + + if (!hasRole) { + throw new Error(`User ${token.sub} no longer has the required role`); + } + return token; }, }, pages: { diff --git a/src/app/lib/discord.ts b/src/app/lib/discord.ts index 138e611..340bc3c 100644 --- a/src/app/lib/discord.ts +++ b/src/app/lib/discord.ts @@ -30,23 +30,18 @@ export async function discordFetch(path: string): Promise { } export async function verifyUserRole(userId: string): Promise { - try { - const guildId = requireEnv("DISCORD_GUILD_ID"); - const requiredRoleId = requireEnv("DISCORD_REQUIRED_ROLE_ID"); + const guildId = requireEnv("DISCORD_GUILD_ID"); + const requiredRoleId = requireEnv("DISCORD_REQUIRED_ROLE_ID"); - const response = await discordFetch(`/guilds/${guildId}/members/${userId}`); + const response = await discordFetch(`/guilds/${guildId}/members/${userId}`); - if (response.status === 404) { - return false; - } - if (!response.ok) { - throw new Error(`Failed to fetch guild member ${userId}: ${response.status} ${response.statusText}`); - } - - const member: DiscordGuildMember = await response.json(); - return member.roles.includes(requiredRoleId); - } catch (error) { - console.error("Error verifying user role:", error); + if (response.status === 404) { return false; } + if (!response.ok) { + throw new Error(`Failed to fetch guild member ${userId}: ${response.status} ${response.statusText}`); + } + + const member: DiscordGuildMember = await response.json(); + return member.roles.includes(requiredRoleId); }