mirror of
https://github.com/coracle-social/coracle.git
synced 2024-10-03 02:10:57 +00:00
Add ndk and nsecbunker login
This commit is contained in:
parent
8b3de91408
commit
a2157a392c
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
@ -38,6 +38,7 @@
|
|||||||
"@capacitor/ios": "^4.7.3",
|
"@capacitor/ios": "^4.7.3",
|
||||||
"@fortawesome/fontawesome-free": "^6.2.1",
|
"@fortawesome/fontawesome-free": "^6.2.1",
|
||||||
"@noble/secp256k1": "^1.7.0",
|
"@noble/secp256k1": "^1.7.0",
|
||||||
|
"@nostr-dev-kit/ndk": "^0.7.0",
|
||||||
"@scure/base": "^1.1.1",
|
"@scure/base": "^1.1.1",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {nip19, nip04, getPublicKey, getEventHash, signEvent} from "nostr-tools"
|
import {nip19, nip04, getPublicKey, getEventHash, signEvent, generatePrivateKey} from "nostr-tools"
|
||||||
|
import NDK, {NDKEvent, NDKNip46Signer, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {error} from "src/util/logger"
|
import {error} from "src/util/logger"
|
||||||
import {synced} from "src/util/misc"
|
import {synced} from "src/util/misc"
|
||||||
@ -6,22 +7,56 @@ import {synced} from "src/util/misc"
|
|||||||
const method = synced("agent/keys/method")
|
const method = synced("agent/keys/method")
|
||||||
const pubkey = synced("agent/keys/pubkey")
|
const pubkey = synced("agent/keys/pubkey")
|
||||||
const privkey = synced("agent/keys/privkey")
|
const privkey = synced("agent/keys/privkey")
|
||||||
|
const bunkerKey = synced("agent/keys/bunkerKey")
|
||||||
const getExtension = () => (window as {nostr?: any}).nostr
|
const getExtension = () => (window as {nostr?: any}).nostr
|
||||||
const canSign = () => ["privkey", "extension"].includes(get(method))
|
const canSign = () => ["bunker", "privkey", "extension"].includes(get(method))
|
||||||
|
|
||||||
// Validate the key before setting it to state by encoding it using bech32.
|
// Validate the key before setting it to state by encoding it using bech32.
|
||||||
// This will error if invalid (this works whether it's a public or a private key)
|
// This will error if invalid (this works whether it's a public or a private key)
|
||||||
const validate = key => nip19.npubEncode(key)
|
const validate = key => nip19.npubEncode(key)
|
||||||
|
|
||||||
|
let ndk, remoteSigner
|
||||||
|
|
||||||
|
const prepareNdk = async (token?: string) => {
|
||||||
|
const localSigner = new NDKPrivateKeySigner(get(bunkerKey))
|
||||||
|
|
||||||
|
ndk = new NDK({
|
||||||
|
explicitRelayUrls: ["wss://relay.f7z.io", "wss://relay.damus.io", "wss://relay.nsecbunker.com"],
|
||||||
|
})
|
||||||
|
|
||||||
|
remoteSigner = new NDKNip46Signer(ndk, get(pubkey), localSigner)
|
||||||
|
remoteSigner.token = token
|
||||||
|
|
||||||
|
ndk.signer = remoteSigner
|
||||||
|
|
||||||
|
await ndk.connect(5000)
|
||||||
|
await remoteSigner.blockUntilReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNDK = async () => {
|
||||||
|
if (!ndk) {
|
||||||
|
await prepareNdk()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ndk
|
||||||
|
}
|
||||||
|
|
||||||
const login = ($method, key) => {
|
const login = ($method, key) => {
|
||||||
method.set($method)
|
method.set($method)
|
||||||
|
|
||||||
|
pubkey.set(null)
|
||||||
|
privkey.set(null)
|
||||||
|
bunkerKey.set(null)
|
||||||
|
|
||||||
if ($method === "privkey") {
|
if ($method === "privkey") {
|
||||||
privkey.set(key)
|
privkey.set(key)
|
||||||
pubkey.set(getPublicKey(key))
|
pubkey.set(getPublicKey(key))
|
||||||
} else {
|
} else if (["pubkey", "extension"].includes($method)) {
|
||||||
privkey.set(null)
|
|
||||||
pubkey.set(key)
|
pubkey.set(key)
|
||||||
|
} else if ($method === "bunker") {
|
||||||
|
pubkey.set(key.pubkey)
|
||||||
|
bunkerKey.set(generatePrivateKey())
|
||||||
|
prepareNdk(key.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +66,20 @@ const clear = () => {
|
|||||||
privkey.set(null)
|
privkey.set(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sign = event => {
|
const sign = async event => {
|
||||||
const $method = get(method)
|
const $method = get(method)
|
||||||
|
|
||||||
event.pubkey = get(pubkey)
|
event.pubkey = get(pubkey)
|
||||||
event.id = getEventHash(event)
|
event.id = getEventHash(event)
|
||||||
|
|
||||||
|
if ($method === "bunker") {
|
||||||
|
const ndkEvent = new NDKEvent(await getNDK(), event)
|
||||||
|
|
||||||
|
await ndkEvent.sign(remoteSigner)
|
||||||
|
|
||||||
|
return ndkEvent.rawEvent()
|
||||||
|
}
|
||||||
|
|
||||||
if ($method === "privkey") {
|
if ($method === "privkey") {
|
||||||
return Object.assign(event, {
|
return Object.assign(event, {
|
||||||
sig: signEvent(event, get(privkey)),
|
sig: signEvent(event, get(privkey)),
|
||||||
@ -53,6 +96,23 @@ const sign = event => {
|
|||||||
const getCrypt = () => {
|
const getCrypt = () => {
|
||||||
const $method = get(method)
|
const $method = get(method)
|
||||||
|
|
||||||
|
if ($method === "bunker") {
|
||||||
|
return {
|
||||||
|
encrypt: async (pubkey, message) => {
|
||||||
|
const ndk = await getNDK()
|
||||||
|
const user = ndk.getUser({hexpubkey: pubkey})
|
||||||
|
|
||||||
|
return ndk.signer.encrypt(user, message)
|
||||||
|
},
|
||||||
|
decrypt: async (pubkey, message) => {
|
||||||
|
const ndk = await getNDK()
|
||||||
|
const user = ndk.getUser({hexpubkey: pubkey})
|
||||||
|
|
||||||
|
return ndk.signer.decrypt(user, message)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($method === "privkey") {
|
if ($method === "privkey") {
|
||||||
const $privkey = get(privkey)
|
const $privkey = get(privkey)
|
||||||
|
|
||||||
@ -104,6 +164,7 @@ export default {
|
|||||||
method,
|
method,
|
||||||
pubkey,
|
pubkey,
|
||||||
privkey,
|
privkey,
|
||||||
|
bunkerKey,
|
||||||
canSign,
|
canSign,
|
||||||
validate,
|
validate,
|
||||||
login,
|
login,
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
import ChatEdit from "src/app/views/ChatEdit.svelte"
|
import ChatEdit from "src/app/views/ChatEdit.svelte"
|
||||||
import LoginConnect from "src/app/views/LoginConnect.svelte"
|
import LoginConnect from "src/app/views/LoginConnect.svelte"
|
||||||
import LoginPrivKey from "src/app/views/LoginPrivKey.svelte"
|
import LoginPrivKey from "src/app/views/LoginPrivKey.svelte"
|
||||||
|
import LoginAdvanced from "src/app/views/LoginAdvanced.svelte"
|
||||||
import LoginPubKey from "src/app/views/LoginPubKey.svelte"
|
import LoginPubKey from "src/app/views/LoginPubKey.svelte"
|
||||||
|
import LoginBunker from "src/app/views/LoginBunker.svelte"
|
||||||
import Onboarding from "src/app/views/Onboarding.svelte"
|
import Onboarding from "src/app/views/Onboarding.svelte"
|
||||||
import NoteCreate from "src/app/views/NoteCreate.svelte"
|
import NoteCreate from "src/app/views/NoteCreate.svelte"
|
||||||
import NoteDelete from "src/app/views/NoteDelete.svelte"
|
import NoteDelete from "src/app/views/NoteDelete.svelte"
|
||||||
@ -50,8 +52,12 @@
|
|||||||
<ChatEdit {...m} />
|
<ChatEdit {...m} />
|
||||||
{:else if m.type === "login/privkey"}
|
{:else if m.type === "login/privkey"}
|
||||||
<LoginPrivKey />
|
<LoginPrivKey />
|
||||||
|
{:else if m.type === "login/advanced"}
|
||||||
|
<LoginAdvanced />
|
||||||
{:else if m.type === "login/pubkey"}
|
{:else if m.type === "login/pubkey"}
|
||||||
<LoginPubKey />
|
<LoginPubKey />
|
||||||
|
{:else if m.type === "login/bunker"}
|
||||||
|
<LoginBunker />
|
||||||
{:else if m.type === "login/connect"}
|
{:else if m.type === "login/connect"}
|
||||||
<LoginConnect />
|
<LoginConnect />
|
||||||
{:else if m.type === "person/feed"}
|
{:else if m.type === "person/feed"}
|
||||||
|
@ -32,8 +32,6 @@
|
|||||||
const links = getLinks(shortContent)
|
const links = getLinks(shortContent)
|
||||||
const extraLinks = without(links, getLinks(fullContent))
|
const extraLinks = without(links, getLinks(fullContent))
|
||||||
|
|
||||||
console.log(fullContent, shortContent)
|
|
||||||
|
|
||||||
export const isNewline = i =>
|
export const isNewline = i =>
|
||||||
!shortContent[i] ||
|
!shortContent[i] ||
|
||||||
shortContent[i].type === NEWLINE ||
|
shortContent[i].type === NEWLINE ||
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
modal.push({type: "onboarding", stage: "intro"})
|
modal.push({type: "onboarding", stage: "intro"})
|
||||||
}
|
}
|
||||||
|
|
||||||
const pubkeyLogIn = () => {
|
const advancedLogIn = () => {
|
||||||
modal.push({type: "login/pubkey"})
|
modal.push({type: "login/advanced"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.getPubkey()) {
|
if (user.getPubkey()) {
|
||||||
@ -56,7 +56,7 @@
|
|||||||
>Log In</Anchor>
|
>Log In</Anchor>
|
||||||
<Anchor class="w-32 text-center" theme="button" on:click={signUp}>Sign Up</Anchor>
|
<Anchor class="w-32 text-center" theme="button" on:click={signUp}>Sign Up</Anchor>
|
||||||
</div>
|
</div>
|
||||||
<Anchor on:click={pubkeyLogIn}>
|
<Anchor on:click={advancedLogIn}>
|
||||||
<i class="fa fa-cogs" /> Advanced Login
|
<i class="fa fa-cogs" /> Advanced Login
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</div>
|
</div>
|
||||||
|
42
src/app/views/LoginAdvanced.svelte
Normal file
42
src/app/views/LoginAdvanced.svelte
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {modal} from "src/partials/state"
|
||||||
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
import Card from "src/partials/Card.svelte"
|
||||||
|
import Content from "src/partials/Content.svelte"
|
||||||
|
import Heading from "src/partials/Heading.svelte"
|
||||||
|
|
||||||
|
const BUNKER_URL = "https://nsecbunker.com"
|
||||||
|
|
||||||
|
const pubkeyLogIn = () => {
|
||||||
|
modal.push({type: "login/pubkey"})
|
||||||
|
}
|
||||||
|
|
||||||
|
const bunkerLogIn = () => {
|
||||||
|
modal.push({type: "login/bunker"})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Content size="lg">
|
||||||
|
<Content class="text-center">
|
||||||
|
<Heading>Advanced Login</Heading>
|
||||||
|
<p>Find more options for logging in to nostr below.</p>
|
||||||
|
</Content>
|
||||||
|
<Content gap="gap-4">
|
||||||
|
<Card interactive on:click={pubkeyLogIn}>
|
||||||
|
<Content gap="gap-4">
|
||||||
|
<h2 class="text-lg font-bold">Log in with public key</h2>
|
||||||
|
<p>Use this to access a read-only view of your account (or someone else's).</p>
|
||||||
|
</Content>
|
||||||
|
</Card>
|
||||||
|
<Card interactive on:click={bunkerLogIn}>
|
||||||
|
<Content gap="gap-4">
|
||||||
|
<h2 class="text-lg font-bold">Log in with Nsec Bunker</h2>
|
||||||
|
<p>
|
||||||
|
Keep your keys secure by storing them in a <Anchor external href={BUNKER_URL}
|
||||||
|
>bunker</Anchor
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</Content>
|
||||||
|
</Card>
|
||||||
|
</Content>
|
||||||
|
</Content>
|
43
src/app/views/LoginBunker.svelte
Normal file
43
src/app/views/LoginBunker.svelte
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {toHex} from "src/util/nostr"
|
||||||
|
import Input from "src/partials/Input.svelte"
|
||||||
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
import Content from "src/partials/Content.svelte"
|
||||||
|
import Heading from "src/partials/Heading.svelte"
|
||||||
|
import keys from "src/agent/keys"
|
||||||
|
import {toast} from "src/partials/state"
|
||||||
|
import {login} from "src/app/state"
|
||||||
|
|
||||||
|
let input = ""
|
||||||
|
|
||||||
|
const logIn = () => {
|
||||||
|
const [npub, token] = input.split("#")
|
||||||
|
const pubkey = npub.startsWith("npub") ? toHex(npub) : npub
|
||||||
|
|
||||||
|
try {
|
||||||
|
keys.validate(pubkey)
|
||||||
|
} catch (e) {
|
||||||
|
toast.show("error", "Sorry, but that's an invalid public key.")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
login("bunker", {pubkey, token})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Content size="lg" class="text-center">
|
||||||
|
<Heading>Login with NsecBunker</Heading>
|
||||||
|
<p>
|
||||||
|
To log in remotely, enter your nsec bunker token or pubkey below. If you're not using a token,
|
||||||
|
you'll need to approve authorization requests in your bunker's admin interface.
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<Input bind:value={input} placeholder="npub...">
|
||||||
|
<i slot="before" class="fa fa-key" />
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
<Anchor theme="button" on:click={logIn}>Log In</Anchor>
|
||||||
|
</div>
|
||||||
|
</Content>
|
@ -12,7 +12,7 @@
|
|||||||
import keys from "src/agent/keys"
|
import keys from "src/agent/keys"
|
||||||
import {toast} from "src/partials/state"
|
import {toast} from "src/partials/state"
|
||||||
|
|
||||||
const {pubkey, privkey} = keys
|
const {pubkey, privkey, bunkerKey} = keys
|
||||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||||
const keypairUrl = "https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/"
|
const keypairUrl = "https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/"
|
||||||
|
|
||||||
@ -87,6 +87,21 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $bunkerKey}
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<strong>Bunker Key</strong>
|
||||||
|
<Input disabled type="password" value={$bunkerKey}>
|
||||||
|
<button
|
||||||
|
slot="after"
|
||||||
|
class="fa-solid fa-copy cursor-pointer"
|
||||||
|
on:click={() => copyKey("bunker", $bunkerKey)} />
|
||||||
|
</Input>
|
||||||
|
<p class="text-sm text-gray-1">
|
||||||
|
Your bunker key is used to authorize Coracle with your nsec bunker to sign events on
|
||||||
|
your behalf. Save this if you would like to log in elsewhere without re-authorizing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user