mirror of
https://github.com/UiO-Gaming/name-100-uio-gamers.git
synced 2026-11-05 13:56:53 +01:00
require specified role to use website
This commit is contained in:
6 files changed
+141
-1
No files matched your search
+4
-1
@@ -1,3 +1,6 @@
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
NEXTAUTH_SECRET=
|
||||
NEXTAUTH_SECRET=
|
||||
DISCORD_GUILD_ID=
|
||||
DISCORD_REQUIRED_ROLE_ID=
|
||||
DISCORD_BOT_TOKEN=
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user