diff options
| author | Adam Soltys <[email protected]> | 2026-02-27 14:51:17 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-27 14:52:44 -0800 |
| commit | 11fbf552ebbb9d1d5ae6823893ee80ae23f450a0 (patch) | |
| tree | bc45202062f9aa52233781b164e1014662a4d9de /src | |
| parent | 8ffcf769052461e03a2c69220392cb37a623f3f4 (diff) | |
| download | coinos-ui-11fbf552ebbb9d1d5ae6823893ee80ae23f450a0.tar.gz coinos-ui-11fbf552ebbb9d1d5ae6823893ee80ae23f450a0.zip | |
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 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/routes/(static)/login/+page.server.ts | 51 |
1 files changed, 51 insertions, 0 deletions
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; |
