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_ID=
|
||||||
DISCORD_CLIENT_SECRET=
|
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 type { NextAuthOptions } from "next-auth";
|
||||||
import NextAuth from "next-auth";
|
import NextAuth from "next-auth";
|
||||||
import DiscordProvider, { type DiscordProfile } from "next-auth/providers/discord";
|
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,
|
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",
|
subject: "UiO Gamers",
|
||||||
loginPrompt: "Please log in to play",
|
loginPrompt: "Please log in to play",
|
||||||
loginWithDiscord: "Login with Discord",
|
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: {
|
no: {
|
||||||
submittedAnswers: "Dine svar",
|
submittedAnswers: "Dine svar",
|
||||||
@@ -32,5 +35,8 @@ export const translations = {
|
|||||||
subject: "UiO-Gamere",
|
subject: "UiO-Gamere",
|
||||||
loginPrompt: "Vennligst logg inn for å spille",
|
loginPrompt: "Vennligst logg inn for å spille",
|
||||||
loginWithDiscord: "Logg inn med Discord",
|
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