Files
EpicNext-Cms/src/actions/auth-precheck.ts
T
Simo e668fa85ec 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).
2026-06-28 14:25:19 +02:00

39 lines
1.1 KiB
TypeScript

"use server";
import { checkLogin } from "@/lib/auth/password";
import { prisma } from "@/lib/prisma";
import { env } from "@/env";
export type PrecheckResult = "ok" | "invalid" | "twofactor";
/**
* Validates username+password WITHOUT creating a session, and reports whether a
* TOTP code is still required. Lets the login form do the two-step 2FA flow.
*/
export async function precheckLogin(
username: string,
password: string,
): Promise<PrecheckResult> {
const u = String(username ?? "").trim();
const p = String(password ?? "");
if (!u || !p) return "invalid";
let user: { password: string; twoFactorConfirmedAt: Date | null } | null = null;
try {
user = await prisma.user.findUnique({
where: { username: u },
select: { password: true, twoFactorConfirmedAt: true },
});
} catch {
return "invalid";
}
if (!user) return "invalid";
const res = await checkLogin(p, user.password, {
convertPasswords: env.CONVERT_PASSWORDS,
});
if (!res.valid) return "invalid";
return user.twoFactorConfirmedAt ? "twofactor" : "ok";
}