Improve authentication recovery and mobile gameplay

This commit is contained in:
2026-10-07 11:10:29 +05:00
parent 233e4d57f0
commit ce26e6fdbc
22 changed files with 763 additions and 97 deletions

View File

@@ -8,10 +8,12 @@ import {
authDestination,
passwordUpdateSchema,
profileUpdateSchema,
initialPasswordSchema,
} from "@/shared/account";
import { createHash } from "node:crypto";
import { recoveryCookie, verifyRecoveryGrant } from "./password-recovery";
import { requestOrigin } from "./request-origin";
import { issueInitialPassword } from "@/shared/initial-password";
type Context = {
jsonBody: (r: NextRequest) => Promise<unknown>;
@@ -24,6 +26,40 @@ export async function accountApi(
path: string[],
context: Context,
) {
if (
path[0] === "auth" &&
path[1] === "initial-password" &&
path.length === 2
) {
const account = await user();
if (!account?.email || !account.email_confirmed_at)
return json({ error: "Log in to manage your account" }, 401);
if (request.method === "GET") {
return json({
needed: required(
await admin().rpc("legacy_password_needed", { p_user: account.id }),
),
});
}
if (request.method !== "POST")
return json({ error: "Method not allowed" }, 405);
const body = initialPasswordSchema.parse(await context.jsonBody(request));
await context.limit(`initial-password:${account.id}`, 3, 600);
await issueInitialPassword(account.id, body.password, {
claim: async (id) =>
required(await admin().rpc("claim_legacy_password", { p_user: id })),
update: async (id, password) => {
const { error } = await admin().auth.admin.updateUserById(id, {
password,
});
if (error)
throw new Error(
"Password setup could not be confirmed. Use password recovery before signing out.",
);
},
});
return json({ ok: true });
}
if (path[0] === "profile" && path.length === 1) {
const account = await user();
if (!account) return json({ error: "Log in to manage your account" }, 401);
@@ -63,6 +99,10 @@ export async function accountApi(
const origin = requestOrigin(request, process.env.MINEGEO_URL);
const next = authDestination("next" in body ? body.next : null);
const callback = new URL("/auth/callback", origin);
callback.searchParams.set(
"type",
body.action === "recover" ? "recovery" : "signup",
);
callback.searchParams.set(
"next",
body.action === "recover"
@@ -107,18 +147,9 @@ export async function accountApi(
"Check your email to confirm your account. If you already have an account, log in or reset your password.",
});
}
const { error } =
body.action === "recover"
? await client.auth.resetPasswordForEmail(body.email, {
redirectTo: callback.toString(),
})
: await client.auth.signInWithOtp({
email: body.email,
options: {
shouldCreateUser: false,
emailRedirectTo: callback.toString(),
},
});
const { error } = await client.auth.resetPasswordForEmail(body.email, {
redirectTo: callback.toString(),
});
if (
error &&
!["user_not_found", "signup_disabled"].includes(error.code ?? "")
@@ -126,9 +157,7 @@ export async function accountApi(
throw new Error(error.message);
return json({
message:
body.action === "recover"
? "If an account exists for this email, a password reset link is on its way. Check your inbox."
: "If an account exists for this email, a sign-in link is on its way. Check your inbox.",
"If an account exists for this email, a password reset link is on its way. Check your inbox.",
});
}
if (path[1] !== "password") return json({ error: "Not found" }, 404);