2023-04-30 02:48:22 +00:00
|
|
|
import { type AppContext } from '@/app.ts';
|
2023-08-26 17:48:08 +00:00
|
|
|
import { Conf } from '@/config.ts';
|
2023-08-26 23:03:59 +00:00
|
|
|
import { decryptAdmin, encryptAdmin } from '@/crypto.ts';
|
|
|
|
import { type Event, type EventTemplate, finishEvent, HTTPException } from '@/deps.ts';
|
|
|
|
import { connectResponseSchema } from '@/schemas/nostr.ts';
|
2023-08-27 03:49:32 +00:00
|
|
|
import { jsonSchema } from '@/schema.ts';
|
2023-08-26 23:03:59 +00:00
|
|
|
import { Sub } from '@/subs.ts';
|
|
|
|
import { Time } from '@/utils.ts';
|
|
|
|
import { createAdminEvent } from '@/utils/web.ts';
|
2023-05-21 02:16:14 +00:00
|
|
|
|
2023-05-14 01:16:44 +00:00
|
|
|
/**
|
|
|
|
* Sign Nostr event using the app context.
|
|
|
|
*
|
|
|
|
* - If a secret key is provided, it will be used to sign the event.
|
2023-08-26 23:03:59 +00:00
|
|
|
* - If `X-Nostr-Sign` is passed, it will use a NIP-46 to sign the event.
|
2023-05-14 01:16:44 +00:00
|
|
|
*/
|
2023-08-17 02:53:51 +00:00
|
|
|
async function signEvent<K extends number = number>(event: EventTemplate<K>, c: AppContext): Promise<Event<K>> {
|
2023-05-07 20:43:38 +00:00
|
|
|
const seckey = c.get('seckey');
|
2023-08-26 23:03:59 +00:00
|
|
|
const header = c.req.headers.get('x-nostr-sign');
|
|
|
|
|
|
|
|
if (seckey) {
|
|
|
|
return finishEvent(event, seckey);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (header) {
|
|
|
|
return await signNostrConnect(event, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new HTTPException(400, {
|
|
|
|
res: c.json({ id: 'ditto.sign', error: 'Unable to sign event' }, 400),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Sign event with NIP-46, waiting in the background for the signed event. */
|
|
|
|
async function signNostrConnect<K extends number = number>(event: EventTemplate<K>, c: AppContext): Promise<Event<K>> {
|
|
|
|
const pubkey = c.get('pubkey');
|
|
|
|
|
|
|
|
if (!pubkey) {
|
|
|
|
throw new HTTPException(401);
|
2023-05-14 00:46:47 +00:00
|
|
|
}
|
2023-05-07 20:43:38 +00:00
|
|
|
|
2023-08-26 23:03:59 +00:00
|
|
|
const messageId = crypto.randomUUID();
|
|
|
|
|
|
|
|
createAdminEvent({
|
|
|
|
kind: 24133,
|
|
|
|
content: await encryptAdmin(
|
|
|
|
pubkey,
|
|
|
|
JSON.stringify({
|
|
|
|
id: messageId,
|
|
|
|
method: 'sign_event',
|
|
|
|
params: [event],
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
tags: [['p', pubkey]],
|
|
|
|
}, c);
|
|
|
|
|
|
|
|
return awaitSignedEvent<K>(pubkey, messageId, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Wait for signed event to be sent through Nostr relay. */
|
2023-09-02 23:56:20 +00:00
|
|
|
async function awaitSignedEvent<K extends number = number>(
|
2023-08-26 23:03:59 +00:00
|
|
|
pubkey: string,
|
|
|
|
messageId: string,
|
|
|
|
c: AppContext,
|
|
|
|
): Promise<Event<K>> {
|
|
|
|
const sub = Sub.sub(messageId, '1', [{ kinds: [24133], authors: [pubkey], '#p': [Conf.pubkey] }]);
|
|
|
|
|
|
|
|
function close(): void {
|
|
|
|
Sub.close(messageId);
|
2023-05-07 20:43:38 +00:00
|
|
|
}
|
2023-04-30 02:48:22 +00:00
|
|
|
|
2023-09-02 23:56:20 +00:00
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
close();
|
|
|
|
throw new HTTPException(408, {
|
|
|
|
res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }),
|
|
|
|
});
|
|
|
|
}, Time.minutes(1));
|
|
|
|
|
|
|
|
for await (const event of sub) {
|
|
|
|
if (event.kind === 24133) {
|
|
|
|
const decrypted = await decryptAdmin(event.pubkey, event.content);
|
|
|
|
const msg = jsonSchema.pipe(connectResponseSchema).parse(decrypted);
|
|
|
|
|
|
|
|
if (msg.id === messageId) {
|
|
|
|
close();
|
|
|
|
clearTimeout(timeout);
|
|
|
|
return msg.result as Event<K>;
|
2023-08-26 23:03:59 +00:00
|
|
|
}
|
2023-09-02 23:56:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should never happen.
|
|
|
|
throw new HTTPException(500, {
|
|
|
|
res: c.json({ id: 'ditto.sign', error: 'Unable to sign event' }, 500),
|
2023-08-26 23:03:59 +00:00
|
|
|
});
|
2023-08-26 17:48:08 +00:00
|
|
|
}
|
2023-04-30 02:48:22 +00:00
|
|
|
|
2023-08-26 17:48:08 +00:00
|
|
|
/** Sign event as the Ditto server. */
|
|
|
|
// deno-lint-ignore require-await
|
|
|
|
async function signAdminEvent<K extends number = number>(event: EventTemplate<K>): Promise<Event<K>> {
|
|
|
|
return finishEvent(event, Conf.seckey);
|
2023-04-30 02:48:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-03 00:33:13 +00:00
|
|
|
export { signAdminEvent, signEvent, signNostrConnect };
|