Re-verify the paying member role every hour during a session

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
leanderandClaude Opus 5.5 committed 2026-10-31 01:42:26 +01:00
1 parent 925d1a4593
commit ecfa50799e
2 files changed
+44 -19

No files matched your search

+34 -4
View File
@@ -2,6 +2,16 @@ import { verifyUserRole } from "@/lib/discord";
import type { NextAuthOptions } from "next-auth"; import type { NextAuthOptions } from "next-auth";
import DiscordProvider, { type DiscordProfile } from "next-auth/providers/discord"; import DiscordProvider, { type DiscordProfile } from "next-auth/providers/discord";
const ROLE_RECHECK_INTERVAL = 60 * 60 * 1000;
const roleChecks = new Map<string, { hasRole: boolean; checkedAt: number }>();
async function checkRole(userId: string): Promise<boolean> {
const hasRole = await verifyUserRole(userId);
roleChecks.set(userId, { hasRole, checkedAt: Date.now() });
return hasRole;
}
export const authOptions: NextAuthOptions = { export const authOptions: NextAuthOptions = {
providers: [ providers: [
DiscordProvider({ DiscordProvider({
@@ -26,13 +36,33 @@ export const authOptions: NextAuthOptions = {
], ],
callbacks: { callbacks: {
async signIn({ user }) { async signIn({ user }) {
const hasRequiredRole = await verifyUserRole(user.id); try {
return await checkRole(user.id);
if (!hasRequiredRole) { } catch (error) {
console.error("Error verifying user role:", error);
return false; 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: { pages: {
+10 -15
View File
@@ -30,23 +30,18 @@ export async function discordFetch(path: string): Promise<Response> {
} }
export async function verifyUserRole(userId: string): Promise<boolean> { export async function verifyUserRole(userId: string): Promise<boolean> {
try { const guildId = requireEnv("DISCORD_GUILD_ID");
const guildId = requireEnv("DISCORD_GUILD_ID"); const requiredRoleId = requireEnv("DISCORD_REQUIRED_ROLE_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) { 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);
return false; 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);
} }