Update configuration and README for improved setup and security; disable default API and emoji sync, and enhance Discord permission checks in API routes.
This commit is contained in:
@@ -38,8 +38,14 @@ import {
|
||||
AdminConfigUpdate
|
||||
} from "@/types/api";
|
||||
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1";
|
||||
const API_KEY = process.env.NEXT_PUBLIC_DASHBOARD_API_KEY;
|
||||
const isServer = typeof window === "undefined";
|
||||
|
||||
/** Server: direct bot API with secret key. Browser: same-origin proxy route. */
|
||||
const BASE_URL = isServer
|
||||
? (process.env.DASHBOARD_API_URL ||
|
||||
process.env.NEXT_PUBLIC_API_URL ||
|
||||
"http://127.0.0.1:8000/api/v1")
|
||||
: "/api/proxy";
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
@@ -55,8 +61,20 @@ async function request<T>(
|
||||
const url = `${BASE_URL}${endpoint}`;
|
||||
|
||||
const headers = new Headers(options.headers);
|
||||
if (API_KEY) {
|
||||
headers.set("Authorization", `Bearer ${API_KEY}`);
|
||||
if (isServer) {
|
||||
const apiKey = process.env.DASHBOARD_API_KEY;
|
||||
if (apiKey) {
|
||||
headers.set("Authorization", `Bearer ${apiKey}`);
|
||||
}
|
||||
const { getServerSession } = await import("next-auth/next");
|
||||
const { authOptions } = await import("@/lib/auth");
|
||||
const session = await getServerSession(authOptions);
|
||||
if (session?.accessToken) {
|
||||
headers.set("X-Discord-Access-Token", session.accessToken);
|
||||
}
|
||||
if (session?.user?.id) {
|
||||
headers.set("X-Discord-User-Id", session.user.id);
|
||||
}
|
||||
}
|
||||
headers.set("Content-Type", "application/json");
|
||||
try {
|
||||
|
||||
45
dashboard/lib/guild-auth.ts
Normal file
45
dashboard/lib/guild-auth.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Discord guild permission helpers for dashboard access control.
|
||||
*/
|
||||
|
||||
export interface DiscordGuild {
|
||||
id: string;
|
||||
permissions: string;
|
||||
owner?: boolean;
|
||||
}
|
||||
|
||||
const MANAGE_GUILD = BigInt(0x20);
|
||||
const ADMINISTRATOR = BigInt(0x8);
|
||||
|
||||
export function canManageGuild(guild: DiscordGuild): boolean {
|
||||
if (guild.owner) return true;
|
||||
try {
|
||||
const perms = BigInt(guild.permissions);
|
||||
return (
|
||||
(perms & ADMINISTRATOR) === ADMINISTRATOR ||
|
||||
(perms & MANAGE_GUILD) === MANAGE_GUILD
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchUserGuilds(
|
||||
accessToken: string
|
||||
): Promise<DiscordGuild[]> {
|
||||
const res = await fetch("https://discord.com/api/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function userCanManageGuild(
|
||||
accessToken: string,
|
||||
guildId: string
|
||||
): Promise<boolean> {
|
||||
const guilds = await fetchUserGuilds(accessToken);
|
||||
const guild = guilds.find((g) => String(g.id) === String(guildId));
|
||||
return guild ? canManageGuild(guild) : false;
|
||||
}
|
||||
@@ -23,6 +23,11 @@ export function cn(...inputs: ClassValue[]) {
|
||||
|
||||
export function isAdmin(userId?: string | null) {
|
||||
if (!userId) return false;
|
||||
const adminIds = (process.env.NEXT_PUBLIC_ADMIN_IDS || "").split(",");
|
||||
const adminIds = [
|
||||
...(process.env.ADMIN_IDS || "").split(","),
|
||||
...(process.env.NEXT_PUBLIC_ADMIN_IDS || "").split(","),
|
||||
]
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean);
|
||||
return adminIds.includes(userId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user