diff options
| author | Adam Soltys <[email protected]> | 2024-12-29 18:19:47 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-12-29 18:19:47 -0800 |
| commit | 83be80fe3d5f4179c66cd4f4addae50927e2485b (patch) | |
| tree | cd6a790009dd22183e7921b579139d490835a4ae | |
| parent | bb277198e50ab3d237ee7a6cba4b35a8105e8698 (diff) | |
| parent | 6ec41bac23d941b81b93883d00602c8ccec9ff35 (diff) | |
| download | coinos-ui-83be80fe3d5f4179c66cd4f4addae50927e2485b.tar.gz coinos-ui-83be80fe3d5f4179c66cd4f4addae50927e2485b.zip | |
Merge branch 'nostr-login'
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 9 | ||||
| -rw-r--r-- | src/routes/(static)/login/+page.server.ts | 44 | ||||
| -rw-r--r-- | src/routes/(static)/login/+page.svelte | 73 |
3 files changed, 114 insertions, 12 deletions
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 05273bc5..53a994ad 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,4 +1,6 @@ <script> + import { browser } from "$app/environment"; + import { onMount } from "svelte"; import { scale } from "svelte/transition"; import { btc, f, sat } from "$lib/utils"; import Account from "$comp/Account.svelte"; @@ -23,6 +25,13 @@ }; if (user) user.savings = 0; + + let pk = $state();; + onMount(async () => { + if (browser) { + pk = await window.nostr.getPublicKey(); + } + }); </script> <div class="space-y-2"> diff --git a/src/routes/(static)/login/+page.server.ts b/src/routes/(static)/login/+page.server.ts index 6e268c92..740f02d8 100644 --- a/src/routes/(static)/login/+page.server.ts +++ b/src/routes/(static)/login/+page.server.ts @@ -1,13 +1,15 @@ -import { fd, login } from "$lib/utils"; +import { fd, get, login } from "$lib/utils"; import { fail, redirect } from "@sveltejs/kit"; export const load = async ({ parent }) => { const { user } = await parent(); if (user?.pubkey) redirect(307, `/${user.username}`); + const { id } = await get("/challenge"); + return { id }; }; export const actions = { - default: async ({ cookies, request }) => { + login: async ({ cookies, request }) => { const form = await fd(request); let { username, password, token, loginRedirect } = form; const user = { username, password, token }; @@ -23,4 +25,42 @@ export const actions = { redirect(307, loginRedirect || `/${user.username}`); }, + + nostr: async ({ cookies, fetch, request }) => { + const form = await fd(request); + let { id, event, loginRedirect } = form; + if (loginRedirect === "null") loginRedirect = undefined; + event = JSON.parse(event); + + const maxAge = 380 * 24 * 60 * 60; + + const res = await fetch("/api/nostrLogin", { + method: "POST", + body: JSON.stringify({ id, event }), + headers: { + "content-type": "application/json", + accept: "application/json", + "cf-connecting-ip": request.headers.get("cf-connecting-ip"), + }, + }); + + if (res.status === 401) { + const text = await res.text(); + if (text.startsWith("2fa")) throw new Error("2fa"); + } + + const { user, token } = await res.json(); + if (!token) throw new Error("Login failed"); + const { username, 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", username, opts); + cookies.set("token", token, opts); + + redirect(307, loginRedirect || `/${username}`); + }, }; diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte index 5caf48fb..b49ba036 100644 --- a/src/routes/(static)/login/+page.svelte +++ b/src/routes/(static)/login/+page.svelte @@ -1,5 +1,5 @@ <script> - import { run, preventDefault } from "svelte/legacy"; + import { browser } from "$app/environment"; import handler from "$lib/handler"; import { onMount } from "svelte"; @@ -16,7 +16,9 @@ import { generate } from "$lib/nostr"; import { invalidateAll } from "$app/navigation"; + let nostr = $state(); onMount(() => { + if (browser && window.nostr) nostr = true; $pin = undefined; localStorage.clear(); sessionStorage.clear(); @@ -25,11 +27,11 @@ if (lang) document.cookie = `lang=${lang} ;`; }); - let { form } = $props(); + let { data, form } = $props(); + let { id } = $derived(data); let token = $state(""); let code = []; - let redirect; $password = undefined; @@ -48,6 +50,7 @@ let revealPassword = $state(false); async function handleSubmit(e) { + e.preventDefault(); let data = new FormData(this); let user = Object.fromEntries(data); @@ -55,7 +58,7 @@ data.set(k, user[k]); } - const response = await fetch("/login", { + const response = await fetch("?/login", { method: "POST", body: data, }); @@ -75,6 +78,47 @@ btn.click(); }); }); + + let nostrLogin = async () => { + let pubkey = await window.nostr.getPublicKey(); + const formData = new FormData(); + + let ev = { + kind: 1, + pubkey, + created_at: Date.now(), + content: id, + tags: [], + }; + + try { + let signedEvent = await window.nostr.signEvent(ev); + + formData.append("loginRedirect", redirect); + formData.append("token", token); + formData.append("event", JSON.stringify(signedEvent)); + formData.append("id", id); + + let response = await fetch("?/nostr", { + method: "POST", + body: formData, + }); + + const result = deserialize(await response.text()); + + if (result.type === "success") { + await invalidateAll(); + } + + applyAction(result); + } catch (e) { + fail(e.message); + } + }; + + let redirect = $derived( + $loginRedirect || $page.url.searchParams.get("redirect"), + ); </script> <div @@ -88,12 +132,14 @@ </div> {/if} - <form use:enhance class="space-y-5" onsubmit={handleSubmit} method="POST"> - <input - type="hidden" - name="loginRedirect" - value={$loginRedirect || $page.url.searchParams.get("redirect")} - /> + <form + use:enhance + class="space-y-5" + onsubmit={handleSubmit} + method="POST" + action="?/login" + > + <input type="hidden" name="loginRedirect" value={redirect} /> <input type="hidden" name="token" value={token} /> <input @@ -118,6 +164,13 @@ {$t("login.signIn")} </button> + {#if nostr} + <button type="button" class="btn" onclick={nostrLogin}> + <img src="/images/nostr.png" class="w-8" /> + <div class="my-auto">{$t("user.settings.nostr")}</div> + </button> + {/if} + <p class="text-secondary text-center font-medium"> {$t("login.noAccount")} <a |
