/** * ╔══════════════════════════════════════════════════════════════════╗ * ║ ║ * ║ +-+-+-+-+-+-+-+-+ ║ * ║ |H|e|x|a|H|o|s|t| ║ * ║ +-+-+-+-+-+-+-+-+ ║ * ║ ║ * ║ © 2026 HexaHost — All Rights Reserved ║ * ║ ║ * ║ discord ── https://discord.gg/hexahost ║ * ║ github ── https://github.com/theoneandonlymace ║ * ║ ║ * ╚══════════════════════════════════════════════════════════════════╝ */ import DiscordProvider from "next-auth/providers/discord"; import { AuthOptions } from "next-auth"; async function refreshDiscordAccessToken(token: { accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string; [key: string]: unknown; }) { if (!token.refreshToken) { return { ...token, error: "RefreshTokenMissing" as const }; } try { const body = new URLSearchParams({ client_id: process.env.DISCORD_CLIENT_ID || "", client_secret: process.env.DISCORD_CLIENT_SECRET || "", grant_type: "refresh_token", refresh_token: String(token.refreshToken), }); const res = await fetch("https://discord.com/api/oauth2/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); const data = await res.json(); if (!res.ok) { throw data; } return { ...token, accessToken: data.access_token as string, refreshToken: (data.refresh_token as string) || token.refreshToken, expiresAt: Math.floor(Date.now() / 1000) + Number(data.expires_in || 604800), error: undefined, }; } catch (error) { console.error("Discord token refresh failed:", error); return { ...token, error: "RefreshAccessTokenError" as const }; } } export const authOptions: AuthOptions = { providers: [ DiscordProvider({ clientId: process.env.DISCORD_CLIENT_ID || "", clientSecret: process.env.DISCORD_CLIENT_SECRET || "", authorization: { params: { scope: "identify guilds" } }, }), ], callbacks: { async jwt({ token, account }) { // Initial sign-in if (account) { return { ...token, accessToken: account.access_token, refreshToken: account.refresh_token, expiresAt: account.expires_at, }; } const expiresAt = typeof token.expiresAt === "number" ? token.expiresAt : 0; // Refresh when expired, missing expiry, or previous refresh failed const stillValid = Boolean(token.accessToken) && expiresAt > 0 && Date.now() < expiresAt * 1000 - 60_000 && !token.error; if (stillValid) { return token; } return refreshDiscordAccessToken(token as { accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string; }); }, async session({ session, token }) { if (session.user) { // @ts-ignore session.user.id = token.sub; // @ts-ignore session.accessToken = token.accessToken; // @ts-ignore session.error = token.error; } // Surface refresh failures so pages can redirect to sign-in if (token.error) { // @ts-ignore session.error = token.error; } return session; }, }, pages: { signIn: "/", }, };