aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-27 14:51:17 -0800
committerAdam Soltys <[email protected]>2026-02-27 14:52:44 -0800
commit11fbf552ebbb9d1d5ae6823893ee80ae23f450a0 (patch)
treebc45202062f9aa52233781b164e1014662a4d9de /src
parent8ffcf769052461e03a2c69220392cb37a623f3f4 (diff)
downloadcoinos-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.ts51
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;