feat: shop buy, forum replies, tickets, messages, sessions, and UX hardening
Deploy / release (push) Skipped
Deploy / deploy (push) Failing after 4m23s

Complete remaining product gaps: credit-based shop purchases, guild thread replies, help ticket detail/reply/close, offline message compose, sign-out-everywhere via JWT version, ads delete confirm, soft-fail feedback, rate limits, loading states, and single auth() in site layout.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 21:21:02 +02:00
1 parent ed7db6e048
commit 803e8f36c1
47 files changed
+2793 -358

No files matched your search

+56 -4
View File
@@ -135,7 +135,12 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
/* ignore */
}
return { id: String(user.id), name: user.username, rank: user.rank };
return {
id: String(user.id),
name: user.username,
rank: user.rank,
jwtVersion: user.websiteJwtVersion,
};
},
}),
// OAuth providers — enabled only when both id + secret are configured.
@@ -200,6 +205,13 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
return "/login?error=NoAccount";
},
async jwt({ token, user, account }) {
if (user) {
token.jwtVersion =
(user as { jwtVersion?: number }).jwtVersion ??
token.jwtVersion ??
0;
}
if (user && account?.provider === "credentials") {
token.rank = (user as { rank?: number }).rank;
return token;
@@ -228,12 +240,18 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
if (linked) {
const dbUser = await prisma.user.findUnique({
where: { id: Number(linked.userId) },
select: { id: true, rank: true, username: true },
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;
return token;
}
}
@@ -247,22 +265,56 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
try {
const dbUser = await prisma.user.findFirst({
where: { mail: user.email, twoFactorConfirmedAt: null },
select: { id: true, rank: true, username: true },
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;
}
} catch {
// leave token as-is on lookup failure
}
}
if (token.sub && !token.invalid) {
try {
const dbUser = await prisma.user.findUnique({
where: { id: Number(token.sub) },
select: {
websiteJwtVersion: true,
rank: true,
username: true,
},
});
if (
!dbUser ||
(token.jwtVersion ?? 0) !== dbUser.websiteJwtVersion
) {
token.invalid = true;
delete token.sub;
return token;
}
token.rank = dbUser.rank;
token.name = dbUser.username;
} catch {
/* keep session on transient DB errors */
}
}
return token;
},
session({ session, token }) {
if (token.sub && session.user) session.user.id = token.sub;
if (token.invalid || !token.sub) {
return session;
}
if (session.user) session.user.id = token.sub;
if (typeof token.rank === "number" && session.user)
session.user.rank = token.rank;
return session;