Update docs for app session
This commit is contained in:
+163
-66
@@ -6,6 +6,7 @@ The session system provides a unified way to handle different authentication met
|
||||
- NIP-07 via Browser Extension
|
||||
- NIP-46 via Bunker URL or Nostrconnect
|
||||
- NIP-55 via Android Signer Application
|
||||
- Read-only pubkey login
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -16,96 +17,192 @@ Sessions are stored in local storage and can be:
|
||||
- Switched dynamically
|
||||
- Backed by different signing methods
|
||||
|
||||
## Basic Usage
|
||||
## NIP 01 Example
|
||||
|
||||
The simplest type of login is NIP 01, although it's generally a bad idea to be handling user keys. NIP 46, 44, or 07 login are preferable. However, NIP 01 can be useful for supporting signup, local profiles, or ephemeral keys.
|
||||
|
||||
```typescript
|
||||
import {sessions, pubkey, addSession, dropSession} from '@welshman/app'
|
||||
import {makeSecret} from '@welshman/signer'
|
||||
import {loginWithNip01} from '@welshman/app'
|
||||
|
||||
// Add multiple sessions
|
||||
addSession({method: 'nip07', pubkey: 'abc...'})
|
||||
addSession({method: 'nip46', pubkey: 'def...', secret: '123'})
|
||||
|
||||
// Switch between sessions
|
||||
pubkey.set('abc...') // Activates that session
|
||||
|
||||
// Remove a session
|
||||
dropSession('abc...')
|
||||
|
||||
// List all sessions
|
||||
console.log(sessions.get())
|
||||
loginWithNip01(makeSecret())
|
||||
```
|
||||
|
||||
## NIP-46 (Bunker) Authentication
|
||||
## NIP 07 Example
|
||||
|
||||
A simple way to sign in for desktop browser users is using [NIP 07](https://github.com/nostr-protocol/nips/blob/master/07.md). This method is easy to implement, but should be used sparingly, since not all users will be using a browser with a nostr signing extension installed.
|
||||
|
||||
```typescript
|
||||
import {Nip46Broker, Nip46Signer} from '@welshman/signer'
|
||||
import {addSession} from '@welshman/app'
|
||||
import {Nip07Signer} from '@welshman/signer'
|
||||
import {loginWithNip07} from '@welshman/app'
|
||||
|
||||
// Connect to a bunker
|
||||
const clientSecret = makeSecret()
|
||||
const relays = ['wss://relay.damus.io']
|
||||
const broker = Nip46Broker.get({relays, clientSecret})
|
||||
const signer = new Nip07Signer()
|
||||
|
||||
// Generate nostrconnect URL for the bunker
|
||||
const connectUrl = await broker.makeNostrconnectUrl({
|
||||
name: "My App",
|
||||
url: "https://myapp.com"
|
||||
})
|
||||
|
||||
// Wait for user to approve in bunker
|
||||
const response = await broker.waitForNostrconnect(connectUrl)
|
||||
|
||||
// Create session
|
||||
addSession({
|
||||
method: 'nip46',
|
||||
pubkey: response.event.pubkey,
|
||||
secret: clientSecret,
|
||||
handler: {
|
||||
pubkey: response.event.pubkey,
|
||||
relays
|
||||
signer.getPubkey().then(pubkey => {
|
||||
if (pubkey) {
|
||||
loginWithNip07(pubkey)
|
||||
} else {
|
||||
// User extension does not exist or did not respond
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Using Session Signer
|
||||
## NIP-46 Authentication
|
||||
|
||||
The best default signing scheme is [NIP 46](https://github.com/nostr-protocol/nips/blob/master/46.md), AKA "Nostr Connect". This supports multiple handshakes depending on desired UX, and can support advanced use cases like secure enclaves, self-hosted keys, and FROST multisig.
|
||||
|
||||
The simpler `bunker://` handshake is done by asking the user to provide a bunker URL, either by QR code, or by pasting it manually into your application.
|
||||
|
||||
```typescript
|
||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||
import {loginWithNip46, nip46Perms} from "@welshman/app"
|
||||
import {isKeyValid} from "src/util/nostr"
|
||||
|
||||
// Make a client secret - this is distinct from the user's private key, and is used
|
||||
// for communicating securely with the remote signer
|
||||
const clientSecret = makeSecret()
|
||||
|
||||
// Ask the user to input their bunker URL
|
||||
const bunkerUrl = prompt("Please enter your bunker url")
|
||||
|
||||
// Pase the bunker url
|
||||
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(bunkerUrl)
|
||||
|
||||
if (!isKeyValid(signerPubkey)) {
|
||||
alert("Sorry, but that's an invalid public key.")
|
||||
} else if (relays.length === 0) {
|
||||
alert("That connection string doesn't have any relays.")
|
||||
} else {
|
||||
// Open up a connection with the signer
|
||||
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
|
||||
|
||||
// Send a connect request with the default permissions
|
||||
const result = await broker.connect(connectSecret, nip46Perms)
|
||||
|
||||
// Make sure to check the connect secret to prevent hijacking
|
||||
if (result === connectSecret) {
|
||||
// Get the user's public key
|
||||
const pubkey = await broker.getPublicKey()
|
||||
|
||||
if (!pubkey) {
|
||||
alert("Failed to initialize session")
|
||||
} else {
|
||||
loginWithNip46(pubkey, clientSecret, signerPubkey, relays)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can provide the user with a `nostrconnect://` URL which they can copy or scan with their signer. This is a better UX for users using a signer on their mobile phone.
|
||||
|
||||
```typescript
|
||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||
import {loginWithNip46, nip46Perms} from "@welshman/app"
|
||||
|
||||
// Create a client secret
|
||||
const clientSecret = makeSecret()
|
||||
|
||||
// Stop listening if the user cancels login
|
||||
const abortController = new AbortController()
|
||||
|
||||
// Customize to use relays the signer can send responses to
|
||||
const relays = ['wss://relay.nsec.app/']
|
||||
|
||||
// Create a broker
|
||||
const broker = Nip46Broker.get({clientSecret, relays})
|
||||
|
||||
// Create a nostrconnect:// url
|
||||
const nostrconnect = await broker.makeNostrconnectUrl({
|
||||
name: "My App",
|
||||
url: window.origin,
|
||||
image: window.origin + '/logo.png',
|
||||
perms: nip46Perms,
|
||||
})
|
||||
|
||||
// Share it with the user. Displaying a QR code is particularly helpful
|
||||
alert("To connect, paste this URL into your signer: " + nostrconnect)
|
||||
|
||||
// Listen for the response
|
||||
let response
|
||||
try {
|
||||
response = await broker.waitForNostrconnect(nostrconnect, abortController.signal)
|
||||
} catch (errorResponse: any) {
|
||||
if (errorResponse?.error) {
|
||||
alert(`Received error from signer: ${errorResponse.error}`)
|
||||
} else if (errorResponse) {
|
||||
console.error(errorResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// If we got a response, the broker is already connected and we can log in
|
||||
if (response) {
|
||||
const pubkey = await broker.getPublicKey()
|
||||
|
||||
if (!pubkey) {
|
||||
alert("Failed to initialize session")
|
||||
} else {
|
||||
loginWithNip46(pubkey, clientSecret, response.event.pubkey, relays)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## NIP-55 Authentication
|
||||
|
||||
For the best UX on Android, use [NIP 55](https://github.com/nostr-protocol/nips/blob/master/55.md). Note that this only works for web applications that have been compiled to native Android applications using [CapacitorJS](https://capacitorjs.com/) and [nostr-signer-capacitor-plugin](https://github.com/chebizarro/nostr-signer-capacitor-plugin).
|
||||
|
||||
```typescript
|
||||
import {getNip55, Nip55Signer, loginWithNip55} from "@welshman/signer"
|
||||
|
||||
// Query for installed apps that implement nip 55 signing
|
||||
getNip55().then(signerApps => {
|
||||
// We'll choose the first one and auto-login, but in most cases you'll want to offer a choice
|
||||
if (signerApps.length > 0) {
|
||||
const signer = new Nip55Signer(signerApps[0].packageName)
|
||||
const pubkey = await signer.getPubkey()
|
||||
|
||||
if (pubkey) {
|
||||
loginWithNip55(pubkey, app.packageName)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Read-only session
|
||||
|
||||
A fun feature of nostr is that you can log in as other people, and see what nostr is like from their perspective (minus encrypted data or course).
|
||||
|
||||
```typescript
|
||||
import {loginWithPubkey} from "@welshman/signer"
|
||||
|
||||
// Log in as hodlbod
|
||||
loginWithPubkey("97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322")
|
||||
```
|
||||
|
||||
## Using the current session
|
||||
|
||||
```typescript
|
||||
import {signer, session} from '@welshman/app'
|
||||
import {createEvent, NOTE} from '@welshman/util'
|
||||
|
||||
// Print the current session - be aware the private key is stored in memory, be very
|
||||
// careful about how you handle session objects!
|
||||
console.log(session.get())
|
||||
|
||||
// Current session's signer is always ready to use
|
||||
const event = await signer.get().sign(
|
||||
createEvent(NOTE, {content: "Hello Nostr!"})
|
||||
)
|
||||
|
||||
// hodlbod's pubkey
|
||||
const otherPubkey = "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"
|
||||
|
||||
// Encrypt content for private notes
|
||||
const encrypted = await signer.get().nip44.encrypt(
|
||||
pubkey,
|
||||
"Secret message"
|
||||
)
|
||||
const ciphertext = await signer.get().nip44.encrypt(otherPubkey, "Secret message")
|
||||
|
||||
// Decrypt automatically detects encryption version
|
||||
const plaintext = await decrypt(signer, otherPubkey, ciphertext)
|
||||
```
|
||||
|
||||
## Session Types
|
||||
## Multiple sessions
|
||||
|
||||
```typescript
|
||||
type SessionNip07 = {
|
||||
method: "nip07"
|
||||
pubkey: string
|
||||
}
|
||||
|
||||
type SessionNip46 = {
|
||||
method: "nip46"
|
||||
pubkey: string
|
||||
secret: string
|
||||
handler: {
|
||||
pubkey: string
|
||||
relays: string[]
|
||||
}
|
||||
}
|
||||
|
||||
type SessionNip01 = {
|
||||
method: "nip01"
|
||||
pubkey: string
|
||||
secret: string
|
||||
}
|
||||
```
|
||||
It's possible to support multiple concurrent sessions by simply calling `addSession` multiple times. This will update `sessions`, and set `pubkey` to the most recently added session. You can then switch between sessions by calling `pubkey.set` with a valid session pubkey, and delete sessions using `dropSession(pubkey)`.
|
||||
|
||||
Reference in New Issue
Block a user