Remove Discord and Google OAuth verification from CMS
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m58s

- Remove Discord + Google OAuth providers from NextAuth config
- Remove social login buttons (Discord/Google) from login form
- Delete link-discord.ts action and discord-verify-form.tsx component
- Simplify verify page to email-only verification flow
- Remove connections settings page and its link from settings
- Clean env.ts, .env, .env.example of Discord/Google client vars
- Remove discordUrl social icon from register, login, and homepage
- Clean translation files: remove continueWithDiscord, continueWithGoogle, connections keys
This commit is contained in:
openhands committed 2026-07-24 11:49:16 +02:00
1 parent f7f6e09174
commit 7f8d083763
36 files changed
+34 -994

No files matched your search

+1 -133
View File
@@ -1,7 +1,5 @@
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 { env } from "@/env";
import { LaravelEncrypter } from "@/lib/auth/laravel-encrypter";
import { getCachedJwtVersion } from "@/lib/auth/jwt-version-cache";
@@ -144,67 +142,8 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
};
},
}),
// 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: {
async signIn({ user, account }) {
if (account?.provider === "credentials") return true;
const requireLink = await siteSettings.getBool(
"oauth_require_link",
false,
);
// Always allow explicitly linked accounts.
if (account?.provider === "discord" && account.providerAccountId) {
try {
const linked = await prisma.socialAccounts.findUnique({
where: {
provider_providerId: {
provider: "discord",
providerId: account.providerAccountId,
},
},
select: { userId: true },
});
if (linked) return true;
} catch {
return "/login?error=Unavailable";
}
}
// Email-based binding: only allowed when oauth_require_link is disabled
// AND the matched account does NOT have 2FA enabled (account takeover guard).
if (!requireLink && user.email) {
try {
const dbUser = await prisma.user.findFirst({
where: { mail: user.email, twoFactorConfirmedAt: null },
select: { id: true },
});
if (dbUser) return true;
} catch {
return "/login?error=Unavailable";
}
}
return "/login?error=NoAccount";
},
async jwt({ token, user, account }) {
if (user) {
token.jwtVersion =
@@ -216,81 +155,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
if (user && account?.provider === "credentials") {
token.rank = (user as { rank?: number }).rank;
token.sub = String((user as { id?: string }).id);
return token;
}
// OAuth account linking only when establishing a session — not on
// every subsequent request (avoids siteSettings + DB on each hit).
if (user || account) {
const requireLink = await siteSettings.getBool(
"oauth_require_link",
false,
);
// Try Discord ID via SocialAccounts (always allowed, even when requireLink is true).
if (
!token.sub &&
account?.provider === "discord" &&
account.providerAccountId
) {
try {
const linked = await prisma.socialAccounts.findUnique({
where: {
provider_providerId: {
provider: "discord",
providerId: account.providerAccountId,
},
},
});
if (linked) {
const dbUser = await prisma.user.findUnique({
where: { id: Number(linked.userId) },
select: {
id: true,
rank: true,
username: true,
websiteJwtVersion: true,
},
});
if (dbUser) {
token.sub = String(dbUser.id);
token.rank = dbUser.rank;
token.name = dbUser.username;
token.jwtVersion = dbUser.websiteJwtVersion;
token.jwtCheckedAt = Date.now();
return token;
}
}
} catch {
// leave token as-is on lookup failure
}
}
// Email-based binding: only when requireLink is off AND account has no 2FA.
if (!requireLink && user?.email && !token.sub) {
try {
const dbUser = await prisma.user.findFirst({
where: { mail: user.email, twoFactorConfirmedAt: null },
select: {
id: true,
rank: true,
username: true,
websiteJwtVersion: true,
},
});
if (dbUser) {
token.sub = String(dbUser.id);
token.rank = dbUser.rank;
token.name = dbUser.username;
token.jwtVersion = dbUser.websiteJwtVersion;
token.jwtCheckedAt = Date.now();
}
} catch {
// leave token as-is on lookup failure
}
}
}
// Re-check jwt version at most once per minute (memory/Redis cached).
if (token.sub && !token.invalid) {
const lastCheck =