Add 2FA, email + password reset, and batch-7 pages
Auth (hand-built on the auth core): - 2FA: User model gains two_factor_secret/recovery_codes/confirmed_at (+ idempotent MariaDB migration). authorize() requires a valid TOTP code when 2FA is confirmed (secret decrypted via Laravel APP_KEY, fail-closed). Two-step login (precheckLogin reveals the code field). /settings/2fa enable/confirm/disable flow. - Password reset: nodemailer email service; PasswordReset model + migration; /forgot (request, generic response) + /reset (token sha256 + 1h TTL, sets argon2id hash). Login links to forgot. Batch 7 (parallel agents): /admin/commandocentrum (RCON controls + emulator_errors), social write actions (friend request + guild forum new thread), /help/[category], /badges (public). env: APP_KEY, APP_URL, SMTP_*. Nav extended. Verified: tsc exit 0, vitest 48/48, next build exit 0 (64 page routes).
This commit is contained in:
1 parent
486ce51559
commit
e668fa85ec
24 files changed
+1223
-34
No files matched your search
@@ -2,7 +2,9 @@ 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 { LaravelEncrypter } from "@/lib/auth/laravel-encrypter";
|
||||
import { checkLogin } from "@/lib/auth/password";
|
||||
import { verifyTotp } from "@/lib/auth/totp";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { env } from "@/env";
|
||||
|
||||
@@ -15,6 +17,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
credentials: {
|
||||
username: { label: "Username", type: "text" },
|
||||
password: { label: "Password", type: "password" },
|
||||
code: { label: "2FA code", type: "text" },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
const username = String(credentials?.username ?? "").trim();
|
||||
@@ -37,6 +40,19 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
});
|
||||
}
|
||||
|
||||
// Two-factor: if enabled, a valid TOTP code is required. The secret is
|
||||
// Laravel-encrypted with APP_KEY (fail closed if it cannot be read).
|
||||
if (user.twoFactorConfirmedAt && user.twoFactorSecret) {
|
||||
const code = String(credentials?.code ?? "").trim();
|
||||
if (!code || !env.APP_KEY) return null;
|
||||
try {
|
||||
const secret = new LaravelEncrypter(env.APP_KEY).decrypt(user.twoFactorSecret);
|
||||
if (!verifyTotp(code, secret)) return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return { id: String(user.id), name: user.username, rank: user.rank };
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user