88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
/**
|
|
* NIP-98 HTTP Auth implementation
|
|
* Generates authentication tokens for HTTP requests using Nostr events
|
|
* See: https://github.com/nostr-protocol/nips/blob/master/98.md
|
|
*/
|
|
|
|
import type { EventTemplate } from 'nostr-tools'
|
|
import { finalizeEvent } from 'nostr-tools'
|
|
import { hexToBytes } from 'nostr-tools/utils'
|
|
import { nostrService } from './nostr'
|
|
import { nostrAuthService } from './nostrAuth'
|
|
|
|
/**
|
|
* Generate NIP-98 authentication token for HTTP request
|
|
* @param method HTTP method (GET, POST, etc.)
|
|
* @param url Full URL of the request
|
|
* @param payloadHash Optional SHA256 hash of the request body (for POST/PUT)
|
|
* @returns Base64-encoded signed event token
|
|
*/
|
|
export async function generateNip98Token(method: string, url: string, payloadHash?: string): Promise<string> {
|
|
const pubkey = getPubkeyOrThrow()
|
|
const privateKey = getPrivateKeyOrThrow()
|
|
const eventTemplate = buildNip98EventTemplate({ method, url, payloadHash, pubkey })
|
|
const signedEvent = finalizeEvent(eventTemplate, hexToBytes(privateKey))
|
|
return encodeEventAsBase64Json(signedEvent)
|
|
}
|
|
|
|
function getPubkeyOrThrow(): string {
|
|
const pubkey = nostrService.getPublicKey()
|
|
if (!pubkey) {
|
|
throw new Error('Public key required for NIP-98 authentication. Please unlock your account.')
|
|
}
|
|
return pubkey
|
|
}
|
|
|
|
function getPrivateKeyOrThrow(): string {
|
|
if (!nostrAuthService.isUnlocked()) {
|
|
throw new Error(
|
|
'Private key required for NIP-98 authentication. Please unlock your account with your recovery phrase.'
|
|
)
|
|
}
|
|
const privateKey = nostrAuthService.getPrivateKey()
|
|
if (!privateKey) {
|
|
throw new Error('Private key not available. Please unlock your account.')
|
|
}
|
|
return privateKey
|
|
}
|
|
|
|
function buildNip98EventTemplate(params: {
|
|
method: string
|
|
url: string
|
|
payloadHash: string | undefined
|
|
pubkey: string
|
|
}): EventTemplate & { pubkey: string } {
|
|
const urlObj = new URL(params.url)
|
|
const path = urlObj.pathname + urlObj.search
|
|
const tags: string[][] = [
|
|
['u', urlObj.origin + path],
|
|
['method', params.method],
|
|
]
|
|
if (params.payloadHash) {
|
|
tags.push(['payload', params.payloadHash])
|
|
}
|
|
return {
|
|
kind: 27235,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags,
|
|
content: '',
|
|
pubkey: params.pubkey,
|
|
}
|
|
}
|
|
|
|
function encodeEventAsBase64Json(event: unknown): string {
|
|
const eventJson = JSON.stringify(event)
|
|
const eventBytes = new TextEncoder().encode(eventJson)
|
|
return globalThis.btoa(String.fromCharCode(...eventBytes))
|
|
}
|
|
|
|
/**
|
|
* Check if NIP-98 authentication is available
|
|
* Requires both public key and unlocked private key
|
|
*/
|
|
export function isNip98Available(): boolean {
|
|
const pubkey = nostrService.getPublicKey()
|
|
const isUnlocked = nostrAuthService.isUnlocked()
|
|
return Boolean(pubkey) && isUnlocked
|
|
}
|