require specified role to use website

This commit is contained in:
leander committed 2025-06-22 01:18:34 +02:00
1 parent b4cc07df28
commit 964894d574
6 files changed
+141 -1

No files matched your search

+4 -1
View File
@@ -1,3 +1,6 @@
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
NEXTAUTH_SECRET=
NEXTAUTH_SECRET=
DISCORD_GUILD_ID=
DISCORD_REQUIRED_ROLE_ID=
DISCORD_BOT_TOKEN=
+15
View File
@@ -1,3 +1,4 @@
import { verifyUserRole } from "@/lib/discord";
import type { NextAuthOptions } from "next-auth";
import NextAuth from "next-auth";
import DiscordProvider, { type DiscordProfile } from "next-auth/providers/discord";
@@ -24,6 +25,20 @@ export const authOptions: NextAuthOptions = {
},
}),
],
callbacks: {
async signIn({ user }) {
const hasRequiredRole = await verifyUserRole(user.id);
if (!hasRequiredRole) {
return false;
}
return true;
},
},
pages: {
error: "/auth/error",
},
secret: process.env.NEXTAUTH_SECRET,
};
+35
View File
@@ -0,0 +1,35 @@
"use client";
import LanguageMenu from "@/components/LanguageMenu";
import { useLanguage } from "@/LanguageContext";
import { translations } from "@/translations";
import { signIn } from "next-auth/react";
import { FaDiscord } from "react-icons/fa";
export default function AuthErrorClient() {
const { language } = useLanguage();
const t = translations[language];
return (
<>
<div className="absolute top-4 right-4 z-50 flex items-center gap-2">
<LanguageMenu />
</div>
<div className="flex h-screen flex-col items-center justify-center px-4">
<div className="max-w-md text-center">
<h1 className="mb-4 text-2xl font-bold text-red-500">{t.accessDenied}</h1>
<p className="text-foreground mb-6">{t.accessDeniedMessage}</p>
<div className="flex justify-center">
<button
onClick={() => signIn("discord")}
className="flex cursor-pointer items-center gap-2 rounded-md bg-[#5865F2] px-4 py-2 text-white hover:bg-[#4654C0]"
>
<FaDiscord />
{t.tryAgain}
</button>
</div>
</div>
</div>
</>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { authOptions } from "@/api/auth/[...nextauth]/route";
import { LanguageProvider } from "@/LanguageContext";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import AuthErrorClient from "./AuthErrorClient";
export default async function AuthErrorPage() {
const session = await getServerSession(authOptions);
// If user is already authenticated, redirect to home
if (session) {
redirect("/");
}
return (
<LanguageProvider>
<AuthErrorClient />
</LanguageProvider>
);
}
+61
View File
@@ -0,0 +1,61 @@
interface DiscordGuildMember {
user: {
id: string;
username: string;
discriminator: string;
avatar: string | null;
};
roles: string[];
joined_at: string;
}
export async function verifyUserRole(userId: string): Promise<boolean> {
const guildId = process.env.DISCORD_GUILD_ID;
const requiredRoleId = process.env.DISCORD_REQUIRED_ROLE_ID;
if (!guildId || !requiredRoleId) {
console.error("Missing DISCORD_GUILD_ID or DISCORD_REQUIRED_ROLE_ID environment variables");
return false;
}
try {
// Get the bot token from the Discord application
const botToken = process.env.DISCORD_BOT_TOKEN;
if (!botToken) {
console.error("Missing DISCORD_BOT_TOKEN environment variable");
return false;
}
// Fetch the user's guild member information
const response = await fetch(`https://discord.com/api/v10/guilds/${guildId}/members/${userId}`, {
headers: {
Authorization: `Bot ${botToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
if (response.status === 404) {
// User is not a member of the guild
console.log(`User ${userId} is not a member of guild ${guildId}`);
return false;
}
if (response.status === 403) {
console.error("Bot does not have permission to view guild members");
return false;
}
throw new Error(`Discord API error: ${response.status} ${response.statusText}`);
}
const member: DiscordGuildMember = await response.json();
// Check if the user has the required role
const hasRole = member.roles.includes(requiredRoleId);
console.log(`User ${userId} has role ${requiredRoleId}: ${hasRole}`);
return hasRole;
} catch (error) {
console.error("Error verifying user role:", error);
return false;
}
}
+6
View File
@@ -15,6 +15,9 @@ export const translations = {
subject: "UiO Gamers",
loginPrompt: "Please log in to play",
loginWithDiscord: "Login with Discord",
accessDenied: "Access Denied",
accessDeniedMessage: "You don't have the paying member role in the UiO Gaming Discord server.",
tryAgain: "Try again logging in again",
},
no: {
submittedAnswers: "Dine svar",
@@ -32,5 +35,8 @@ export const translations = {
subject: "UiO-Gamere",
loginPrompt: "Vennligst logg inn for å spille",
loginWithDiscord: "Logg inn med Discord",
accessDenied: "Tilgang nektet",
accessDeniedMessage: 'Du har ikke rollen "Betalende medlem" i UiO Gaming sin Discord-server.',
tryAgain: "Logg på igjen",
},
};