Add social login (Discord/Google) + batch-6 pages
Auth: NextAuth Discord + Google providers (enabled when env id+secret set); OAuth signIn allowed only if a hotel account matches the email; jwt binds the session to that account (id/rank/username). Login page gets social buttons. Batch 6 (parallel agents): /friends (messenger_friendships), /guilds/[id]/forum (threads), /admin/navigation (navigator config), /admin/maintenance (toggle maintenance settings), /admin/alerts (alert_logs + send hotel alert via RCON). Header (Friends) + admin nav (Alerts/Maintenance/Navigator) extended. Verified: tsc exit 0, vitest 48/48, next build exit 0 (57 page routes).
This commit is contained in:
1 parent
73f0f54624
commit
486ce51559
12 files changed
+911
-3
No files matched your search
+43
-3
@@ -1,5 +1,7 @@
|
||||
import NextAuth from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import Discord from "next-auth/providers/discord";
|
||||
import Google from "next-auth/providers/google";
|
||||
import { checkLogin } from "@/lib/auth/password";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { env } from "@/env";
|
||||
@@ -38,13 +40,51 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
return { id: String(user.id), name: user.username, rank: user.rank };
|
||||
},
|
||||
}),
|
||||
// OAuth providers — enabled only when both id + secret are configured.
|
||||
...(env.DISCORD_CLIENT_ID && env.DISCORD_CLIENT_SECRET
|
||||
? [Discord({ clientId: env.DISCORD_CLIENT_ID, clientSecret: env.DISCORD_CLIENT_SECRET })]
|
||||
: []),
|
||||
...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
|
||||
? [Google({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET })]
|
||||
: []),
|
||||
],
|
||||
callbacks: {
|
||||
jwt({ token, user }) {
|
||||
if (user) token.rank = (user as { rank?: number }).rank;
|
||||
async signIn({ user, account }) {
|
||||
if (account?.provider === "credentials") return true;
|
||||
// OAuth: only allow if a hotel account with this email already exists.
|
||||
const email = user.email;
|
||||
if (!email) return "/login?error=NoEmail";
|
||||
try {
|
||||
const dbUser = await prisma.user.findFirst({
|
||||
where: { mail: email },
|
||||
select: { id: true },
|
||||
});
|
||||
return dbUser ? true : "/login?error=NoAccount";
|
||||
} catch {
|
||||
return "/login?error=Unavailable";
|
||||
}
|
||||
},
|
||||
async jwt({ token, user, account }) {
|
||||
if (user && account?.provider === "credentials") {
|
||||
token.rank = (user as { rank?: number }).rank;
|
||||
} else if (user?.email) {
|
||||
// OAuth: bind the session to the matching hotel account.
|
||||
try {
|
||||
const dbUser = await prisma.user.findFirst({
|
||||
where: { mail: user.email },
|
||||
select: { id: true, rank: true, username: true },
|
||||
});
|
||||
if (dbUser) {
|
||||
token.sub = String(dbUser.id);
|
||||
token.rank = dbUser.rank;
|
||||
token.name = dbUser.username;
|
||||
}
|
||||
} catch {
|
||||
// leave token as-is on lookup failure
|
||||
}
|
||||
}
|
||||
return token;
|
||||
},
|
||||
// NextAuth stores the user id in token.sub automatically; surface id + rank.
|
||||
session({ session, token }) {
|
||||
if (token.sub && session.user) session.user.id = token.sub;
|
||||
if (typeof token.rank === "number" && session.user) session.user.rank = token.rank;
|
||||
|
||||
Reference in new issue
Block a user