relay: add support for NIP42 authentication

This commit is contained in:
Lynn Zenn
2023-04-18 15:10:12 +02:00
committed by fiatjaf_
parent 26e35d50e0
commit 6e58fe371c
4 changed files with 94 additions and 11 deletions

View File

@@ -13,6 +13,7 @@ export * as nip13 from './nip13'
export * as nip19 from './nip19'
export * as nip26 from './nip26'
export * as nip39 from './nip39'
export * as nip42 from './nip42'
export * as nip57 from './nip57'
export * as fj from './fakejson'

27
nip42.test.js Normal file
View File

@@ -0,0 +1,27 @@
/* eslint-env jest */
require('websocket-polyfill')
const {
relayInit,
generatePrivateKey,
finishEvent,
nip42
} = require('./lib/nostr.cjs')
test('auth flow', done => {
const relay = relayInit('wss://nostr.kollider.xyz')
relay.connect()
const sk = generatePrivateKey()
relay.on('auth', async challenge => {
await expect(
nip42.authenticate({
challenge,
relay,
sign: e => finishEvent(e, sk)
})
).rejects.toBeTruthy()
relay.close()
done()
})
})

42
nip42.ts Normal file
View File

@@ -0,0 +1,42 @@
import {EventTemplate, Event, Kind} from './event'
import {Relay} from './relay'
/**
* Authenticate via NIP-42 flow.
*
* @example
* const sign = window.nostr.signEvent
* relay.on('auth', challenge =>
* authenticate({ relay, sign, challenge })
* )
*/
export const authenticate = async ({
challenge,
relay,
sign
}: {
challenge: string
relay: Relay
sign: (e: EventTemplate) => Promise<Event>
}): Promise<void> => {
const e: EventTemplate = {
kind: Kind.ClientAuth,
created_at: Math.floor(Date.now() / 1000),
tags: [
['relay', relay.url],
['challenge', challenge]
],
content: ''
}
const sub = relay.publish(await sign(e), 'AUTH')
return new Promise((resolve, reject) => {
sub.on('ok', function ok() {
sub.off('ok', ok)
resolve()
})
sub.on('failed', function fail(reason: string) {
sub.off('failed', fail)
reject(reason)
})
})
}

View File

@@ -4,11 +4,13 @@ import {Event, verifySignature, validateEvent} from './event'
import {Filter, matchFilters} from './filter'
import {getHex64, getSubscriptionId} from './fakejson'
type OutgoingEventType = 'EVENT' | 'AUTH'
type RelayEvent = {
connect: () => void | Promise<void>
disconnect: () => void | Promise<void>
error: () => void | Promise<void>
notice: (msg: string) => void | Promise<void>
auth: (challenge: string) => void | Promise<void>
}
type CountPayload = {
count: number
@@ -26,8 +28,11 @@ export type Relay = {
sub: (filters: Filter[], opts?: SubscriptionOptions) => Sub
list: (filters: Filter[], opts?: SubscriptionOptions) => Promise<Event[]>
get: (filter: Filter, opts?: SubscriptionOptions) => Promise<Event | null>
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
publish: (event: Event) => Pub
count: (
filters: Filter[],
opts?: SubscriptionOptions
) => Promise<CountPayload | null>
publish: (event: Event, type?: OutgoingEventType) => Pub
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
event: T,
listener: U
@@ -61,6 +66,14 @@ export type SubscriptionOptions = {
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
}
const newListeners = (): {[TK in keyof RelayEvent]: RelayEvent[TK][]} => ({
connect: [],
disconnect: [],
error: [],
notice: [],
auth: []
})
export function relayInit(
url: string,
options: {
@@ -73,12 +86,7 @@ export function relayInit(
var ws: WebSocket
var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {}
var listeners: {[TK in keyof RelayEvent]: RelayEvent[TK][]} = {
connect: [],
disconnect: [],
error: [],
notice: []
}
var listeners = newListeners()
var subListeners: {
[subid: string]: {[TK in keyof SubEvent]: SubEvent[TK][]}
} = {}
@@ -198,6 +206,11 @@ export function relayInit(
let notice = data[1]
listeners.notice.forEach(cb => cb(notice))
return
case 'AUTH': {
let challenge = data[1]
listeners.auth?.forEach(cb => cb(challenge))
return
}
}
} catch (err) {
return
@@ -351,11 +364,11 @@ export function relayInit(
resolve(event)
})
}),
publish(event: Event): Pub {
publish(event, type = 'EVENT'): Pub {
if (!event.id) throw new Error(`event ${event} has no id`)
let id = event.id
trySend(['EVENT', event])
trySend([type, event])
return {
on: (type: 'ok' | 'failed', cb: any) => {
@@ -375,7 +388,7 @@ export function relayInit(
},
connect,
close(): void {
listeners = {connect: [], disconnect: [], error: [], notice: []}
listeners = newListeners()
subListeners = {}
pubListeners = {}
if (ws.readyState === WebSocket.OPEN) {