fix(attempt): revoke username

This commit is contained in:
P. Reis 2025-02-27 19:10:13 -03:00
parent 92da5e6ac3
commit e6f4f8d23e
3 changed files with 94 additions and 4 deletions

View file

@ -128,7 +128,7 @@ const adminAccountActionSchema = z.object({
});
const adminActionController: AppController = async (c) => {
const { conf, relay } = c.var;
const { conf, relay, signal } = c.var;
const body = await parseBody(c.req.raw);
const result = adminAccountActionSchema.safeParse(body);
@ -161,7 +161,24 @@ const adminActionController: AppController = async (c) => {
if (data.type === 'revoke_name') {
n.revoke_name = true;
try {
await relay.remove!([{ kinds: [30360], authors: [await conf.signer.getPublicKey()], '#p': [authorId] }]);
const [event] = await relay.query([{
kinds: [30360],
authors: [await conf.signer.getPublicKey()],
'#p': [authorId],
}], { signal });
if (event) {
await createAdminEvent({
kind: 5,
tags: [
['e', event.id],
['k', '30360'],
['p', authorId], // NOTE: this is not in the NIP-09 spec
],
}, c);
} else {
return c.json({ error: 'Name grant not found' }, 404);
}
} catch (e) {
logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) });
return c.json({ error: 'Unexpected runtime error' }, 500);

View file

@ -4,9 +4,10 @@ import { genEvent, MockRelay } from '@nostrify/nostrify/test';
import { assertEquals } from '@std/assert';
import { generateSecretKey, getPublicKey } from 'nostr-tools';
import { DittoRelayStore } from './DittoRelayStore.ts';
import { DittoRelayStore } from '@/storages/DittoRelayStore.ts';
import type { NostrMetadata } from '@nostrify/types';
import { nostrNow } from '@/utils.ts';
Deno.test('updateAuthorData sets nip05', async () => {
const alex = generateSecretKey();
@ -38,6 +39,48 @@ Deno.test('updateAuthorData sets nip05', async () => {
assertEquals(row?.nip05_hostname, 'gleasonator.dev');
});
Deno.test('Admin revokes nip05 grant and nip05 column gets null', async () => {
const alex = generateSecretKey();
await using test = setupTest((req) => {
switch (req.url) {
case 'https://gleasonator.dev/.well-known/nostr.json?name=alex':
return jsonResponse({ names: { alex: getPublicKey(alex) } });
default:
return new Response('Not found', { status: 404 });
}
});
const { db, store, conf } = test;
const metadata: NostrMetadata = { nip05: 'alex@gleasonator.dev' };
const event = genEvent({ kind: 0, content: JSON.stringify(metadata) }, alex);
await store.updateAuthorData(event);
const adminDeletion = await conf.signer.signEvent({
kind: 5,
created_at: nostrNow(),
tags: [
['k', '30360'],
['p', event.id], // NOTE: this is not in the NIP-09 spec
],
content: '',
});
await store.event(adminDeletion);
const row = await db.kysely
.selectFrom('author_stats')
.selectAll()
.where('pubkey', '=', getPublicKey(alex))
.executeTakeFirst();
assertEquals(row?.nip05, null);
assertEquals(row?.nip05_domain, null);
assertEquals(row?.nip05_hostname, null);
});
function setupTest(cb: (req: Request) => Response | Promise<Response>) {
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
@ -53,6 +96,7 @@ function setupTest(cb: (req: Request) => Response | Promise<Response>) {
return {
db,
store,
conf,
[Symbol.asyncDispose]: async () => {
await store[Symbol.asyncDispose]();
await db[Symbol.asyncDispose]();

View file

@ -28,7 +28,7 @@ import { DittoPush } from '@/DittoPush.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { RelayError } from '@/RelayError.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
import { eventAge, nostrNow, Time } from '@/utils.ts';
import { eventAge, isNostrId, nostrNow, Time } from '@/utils.ts';
import { getAmount } from '@/utils/bolt11.ts';
import { errorJson } from '@/utils/log.ts';
import { purifyEvent } from '@/utils/purify.ts';
@ -189,6 +189,7 @@ export class DittoRelayStore implements NRelay {
Promise.allSettled([
this.handleZaps(event),
this.updateAuthorData(event, signal),
this.handleRevokeNip05(event, signal),
this.prewarmLinkPreview(event, signal),
this.generateSetEvents(event),
])
@ -245,6 +246,34 @@ export class DittoRelayStore implements NRelay {
}
}
/** Sets the nip05 column to null */
private async handleRevokeNip05(event: NostrEvent, signal?: AbortSignal) {
const { conf, relay } = this.opts;
if (await conf.signer.getPublicKey() !== event.pubkey) {
return;
}
if (event.kind !== 5) return;
const kind = event.tags.find(([name, value]) => name === 'k' && value === '30360')?.[1];
if (kind !== '30360') {
return;
}
const authorId = event.tags.find(([name]) => name === 'p')?.[1];
if (!authorId || !isNostrId(authorId)) {
return;
}
const [author] = await relay.query([{ kinds: [0], authors: [authorId] }], { signal });
if (!author) {
return;
}
await this.updateAuthorData(author);
}
/** Parse kind 0 metadata and track indexes in the database. */
async updateAuthorData(event: NostrEvent, signal?: AbortSignal): Promise<void> {
if (event.kind !== 0) return;