From 11fbf552ebbb9d1d5ae6823893ee80ae23f450a0 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 27 Feb 2026 14:51:17 -0800 Subject: Add password-derived auth key flow for login and registration Adds deriveAuthKeypair (scrypt-based) and deriveWalletEntropy utilities. Login now attempts challenge-response auth via derived key before password fallback. Registration requires password and derives authPubkey. Settings derives authPubkey on password change. Co-Authored-By: Claude Opus 4.6 --- src/routes/(static)/login/+page.server.ts | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'src') diff --git a/src/routes/(static)/login/+page.server.ts b/src/routes/(static)/login/+page.server.ts index 68143d83..d402fdad 100644 --- a/src/routes/(static)/login/+page.server.ts +++ b/src/routes/(static)/login/+page.server.ts @@ -97,6 +97,57 @@ export const actions = { }; }, + passwordAuth: async ({ cookies, fetch, request }) => { + const form = await fd(request); + let { event, challenge, username, loginRedirect, recaptcha, token: twofa } = form; + if (loginRedirect === "null" || loginRedirect === "undefined") loginRedirect = undefined; + event = JSON.parse(event); + + const maxAge = 380 * 24 * 60 * 60; + + const res = await fetch(`${PUBLIC_COINOS_URL}/authKeyLogin`, { + method: "POST", + body: JSON.stringify({ event, challenge, username, recaptcha, twofa }), + headers: { + "content-type": "application/json", + accept: "application/json", + "cf-connecting-ip": request.headers.get("cf-connecting-ip") ?? "", + "x-forwarded-host": request.headers.get("host") ?? "", + }, + }); + + const text = await res.text(); + + if (res.status === 401) { + if (text.startsWith("2fa")) throw new Error("2fa"); + throw new Error(text); + } + + if (!res.ok) { + let message; + try { + ({ message } = JSON.parse(text)); + } catch { + message = text; + } + return fail(400, { error: message || "Login failed" }); + } + + const { user, token } = JSON.parse(text); + if (!token) return fail(400, { error: "Login failed" }); + const { username: uname, language } = user; + + const expires = new Date(); + expires.setSeconds(expires.getSeconds() + maxAge); + + const opts = { path: "/", expires }; + if (language) cookies.set("lang", language, opts); + cookies.set("username", uname, opts); + cookies.set("token", token, opts); + + redirect(307, loginRedirect || `/${uname}`); + }, + passkey: async ({ cookies, fetch, request }) => { const form = await fd(request); let { credential, challengeId, loginRedirect } = form; -- cgit v1.2.3