✨ Fully rewrite relay
This commit is contained in:
parent
190e38dfc1
commit
20ffbd4c6d
47 changed files with 3489 additions and 128 deletions
331
src/dbEvents/addEventToDb.ts
Normal file
331
src/dbEvents/addEventToDb.ts
Normal file
|
@ -0,0 +1,331 @@
|
|||
import { bytesToHex } from '@noble/ciphers/utils';
|
||||
import { sha512 } from '@noble/hashes/sha2';
|
||||
import * as nostrTools from '@nostr/tools';
|
||||
import { base64 } from '@scure/base';
|
||||
import type { Database } from 'jsr:@db/sqlite';
|
||||
import { POW_TO_MINE } from '../consts.ts';
|
||||
import { handleDeletionEvent } from '../dbEvents/deletionEvent.ts';
|
||||
import {
|
||||
EventAlreadyExistsException,
|
||||
createEncryptedEventForPubkey,
|
||||
} from '../eventEncryptionDecryption.ts';
|
||||
import { publishToRelays } from '../relays.ts';
|
||||
import {
|
||||
isAddressableEvent,
|
||||
isCCNReplaceableEvent,
|
||||
isDeleteEvent,
|
||||
isReplaceableEvent,
|
||||
} from '../utils/eventTypes.ts';
|
||||
import { getCCNPrivateKeyByPubkey } from '../utils/getCCNPrivateKeyByPubkey.ts';
|
||||
import { log } from '../utils/logs.ts';
|
||||
import { sql } from '../utils/queries.ts';
|
||||
import {
|
||||
SecurityEventType,
|
||||
SecuritySeverity,
|
||||
logCCNViolation,
|
||||
logSecurityEvent,
|
||||
} from '../utils/securityLogs.ts';
|
||||
|
||||
export function addEventToDb(
|
||||
db: Database,
|
||||
decryptedEvent: nostrTools.VerifiedEvent,
|
||||
encryptedEvent: nostrTools.VerifiedEvent,
|
||||
ccnPubkey: string,
|
||||
) {
|
||||
log.debug('start', {
|
||||
tag: 'addEventToDb',
|
||||
decryptedId: decryptedEvent.id,
|
||||
encryptedId: encryptedEvent.id,
|
||||
kind: decryptedEvent.kind,
|
||||
ccnPubkey,
|
||||
});
|
||||
const existingEvent = sql`
|
||||
SELECT * FROM events WHERE id = ${decryptedEvent.id}
|
||||
`(db)[0];
|
||||
|
||||
if (existingEvent) throw new EventAlreadyExistsException();
|
||||
|
||||
if (isDeleteEvent(decryptedEvent.kind)) {
|
||||
log.debug('isDeleteEvent, delegating to handleDeletionEvent', {
|
||||
tag: 'addEventToDb',
|
||||
decryptId: decryptedEvent.id,
|
||||
});
|
||||
handleDeletionEvent(db, decryptedEvent, encryptedEvent, ccnPubkey);
|
||||
return;
|
||||
}
|
||||
|
||||
const isInvite =
|
||||
decryptedEvent.tags.findIndex(
|
||||
(tag: string[]) => tag[0] === 'type' && tag[1] === 'invite',
|
||||
) !== -1;
|
||||
|
||||
if (isInvite) {
|
||||
log.debug('isInvite event', { tag: 'addEventToDb' });
|
||||
const shadContent = bytesToHex(
|
||||
sha512.create().update(decryptedEvent.content).digest(),
|
||||
);
|
||||
|
||||
const inviteUsed = sql`
|
||||
SELECT COUNT(*) as count FROM inviter_invitee WHERE invite_hash = ${shadContent}
|
||||
`(db)[0].count;
|
||||
|
||||
if (inviteUsed > 0) {
|
||||
log.debug('invite already used', { tag: 'addEventToDb' });
|
||||
|
||||
logSecurityEvent({
|
||||
eventType: SecurityEventType.INVITE_ALREADY_USED,
|
||||
severity: SecuritySeverity.HIGH,
|
||||
source: 'invite_processing',
|
||||
details: {
|
||||
invite_hash: shadContent,
|
||||
event_id: decryptedEvent.id,
|
||||
ccn_pubkey: ccnPubkey,
|
||||
invitee_pubkey: decryptedEvent.pubkey,
|
||||
},
|
||||
});
|
||||
|
||||
throw new Error('Invite already used');
|
||||
}
|
||||
|
||||
const inviteEvent = sql`
|
||||
SELECT * FROM events WHERE kind = 9999 AND id IN (
|
||||
SELECT event_id FROM event_tags WHERE tag_name = 'i' AND tag_id IN (
|
||||
SELECT tag_id FROM event_tags_values WHERE value_position = 1 AND value = ${shadContent}
|
||||
)
|
||||
)
|
||||
`(db)[0];
|
||||
|
||||
if (!inviteEvent) {
|
||||
log.debug('invite event not found', { tag: 'addEventToDb' });
|
||||
|
||||
logSecurityEvent({
|
||||
eventType: SecurityEventType.INVITE_VALIDATION_FAILURE,
|
||||
severity: SecuritySeverity.HIGH,
|
||||
source: 'invite_processing',
|
||||
details: {
|
||||
error: 'invite_event_not_found',
|
||||
invite_hash: shadContent,
|
||||
event_id: decryptedEvent.id,
|
||||
ccn_pubkey: ccnPubkey,
|
||||
},
|
||||
});
|
||||
|
||||
throw new Error('Invite event not found');
|
||||
}
|
||||
|
||||
const inviterPubkey = inviteEvent.pubkey;
|
||||
const inviteePubkey = decryptedEvent.pubkey;
|
||||
|
||||
db.run('BEGIN TRANSACTION');
|
||||
log.debug('inserting inviter_invitee and allowed_writes', {
|
||||
tag: 'addEventToDb',
|
||||
});
|
||||
sql`
|
||||
INSERT INTO inviter_invitee (ccn_pubkey, inviter_pubkey, invitee_pubkey, invite_hash) VALUES (${ccnPubkey}, ${inviterPubkey}, ${inviteePubkey}, ${shadContent})
|
||||
`(db);
|
||||
|
||||
sql`
|
||||
INSERT INTO allowed_writes (ccn_pubkey, pubkey) VALUES (${ccnPubkey}, ${inviteePubkey})
|
||||
`(db);
|
||||
|
||||
db.run('COMMIT TRANSACTION');
|
||||
log.debug('committed invite transaction', { tag: 'addEventToDb' });
|
||||
|
||||
const allowedPubkeys = sql`
|
||||
SELECT pubkey FROM allowed_writes WHERE ccn_pubkey = ${ccnPubkey}
|
||||
`(db).flatMap((row) => row.pubkey);
|
||||
const ccnName = sql`
|
||||
SELECT name FROM ccns WHERE pubkey = ${ccnPubkey}
|
||||
`(db)[0].name;
|
||||
|
||||
getCCNPrivateKeyByPubkey(ccnPubkey).then((ccnPrivateKey) => {
|
||||
if (!ccnPrivateKey) {
|
||||
log.error('CCN private key not found', { tag: 'addEventToDb' });
|
||||
throw new Error('CCN private key not found');
|
||||
}
|
||||
|
||||
const tags = allowedPubkeys.map((pubkey) => ['p', pubkey]);
|
||||
tags.push(['t', 'invite']);
|
||||
tags.push(['name', ccnName]);
|
||||
|
||||
const privateKeyEvent = nostrTools.finalizeEvent(
|
||||
nostrTools.nip13.minePow(
|
||||
{
|
||||
kind: 9998,
|
||||
created_at: Date.now(),
|
||||
content: base64.encode(ccnPrivateKey),
|
||||
tags,
|
||||
pubkey: ccnPubkey,
|
||||
},
|
||||
POW_TO_MINE,
|
||||
),
|
||||
ccnPrivateKey,
|
||||
);
|
||||
|
||||
const encryptedKeyEvent = createEncryptedEventForPubkey(
|
||||
inviteePubkey,
|
||||
privateKeyEvent,
|
||||
);
|
||||
publishToRelays(encryptedKeyEvent);
|
||||
log.debug('published encryptedKeyEvent to relays', {
|
||||
tag: 'addEventToDb',
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isAllowedWrite = sql`
|
||||
SELECT COUNT(*) as count FROM allowed_writes WHERE ccn_pubkey = ${ccnPubkey} AND pubkey = ${decryptedEvent.pubkey}
|
||||
`(db)[0].count;
|
||||
|
||||
if (isAllowedWrite === 0) {
|
||||
log.debug('not allowed to write to this CCN', {
|
||||
tag: 'addEventToDb',
|
||||
pubkey: decryptedEvent.pubkey,
|
||||
});
|
||||
|
||||
logCCNViolation(
|
||||
SecurityEventType.UNAUTHORIZED_WRITE_ATTEMPT,
|
||||
ccnPubkey,
|
||||
'write_event',
|
||||
{
|
||||
attempted_pubkey: decryptedEvent.pubkey,
|
||||
event_id: decryptedEvent.id,
|
||||
event_kind: decryptedEvent.kind,
|
||||
ccn_pubkey: ccnPubkey,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error('Not allowed to write to this CCN');
|
||||
}
|
||||
|
||||
try {
|
||||
db.run('BEGIN TRANSACTION');
|
||||
log.debug('begin transaction', { tag: 'addEventToDb' });
|
||||
|
||||
if (isReplaceableEvent(decryptedEvent.kind)) {
|
||||
log.debug('isReplaceableEvent, updating replaced events', {
|
||||
tag: 'addEventToDb',
|
||||
});
|
||||
sql`
|
||||
UPDATE events
|
||||
SET replaced = 1
|
||||
WHERE kind = ${decryptedEvent.kind}
|
||||
AND pubkey = ${decryptedEvent.pubkey}
|
||||
AND ccn_pubkey = ${ccnPubkey}
|
||||
AND (created_at < ${decryptedEvent.created_at} OR
|
||||
(created_at = ${decryptedEvent.created_at} AND id > ${decryptedEvent.id}))
|
||||
`(db);
|
||||
}
|
||||
|
||||
if (isAddressableEvent(decryptedEvent.kind)) {
|
||||
log.debug('isAddressableEvent, updating replaced events', {
|
||||
tag: 'addEventToDb',
|
||||
});
|
||||
const dTag = decryptedEvent.tags.find((tag) => tag[0] === 'd')?.[1];
|
||||
if (dTag) {
|
||||
sql`
|
||||
UPDATE events
|
||||
SET replaced = 1
|
||||
WHERE kind = ${decryptedEvent.kind}
|
||||
AND pubkey = ${decryptedEvent.pubkey}
|
||||
AND ccn_pubkey = ${ccnPubkey}
|
||||
AND (created_at < ${decryptedEvent.created_at} OR
|
||||
(created_at = ${decryptedEvent.created_at} AND id > ${decryptedEvent.id}))
|
||||
AND id IN (
|
||||
SELECT event_id FROM event_tags
|
||||
WHERE tag_name = 'd'
|
||||
AND tag_id IN (
|
||||
SELECT tag_id FROM event_tags_values
|
||||
WHERE value_position = 1
|
||||
AND value = ${dTag}
|
||||
)
|
||||
)
|
||||
`(db);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCCNReplaceableEvent(decryptedEvent.kind)) {
|
||||
log.debug('isCCNReplaceableEvent, updating replaced events', {
|
||||
tag: 'addEventToDb',
|
||||
});
|
||||
const dTag = decryptedEvent.tags.find((tag) => tag[0] === 'd')?.[1];
|
||||
log.debug('dTag', { tag: 'addEventToDb', dTag });
|
||||
if (dTag) {
|
||||
sql`
|
||||
UPDATE events
|
||||
SET replaced = 1
|
||||
WHERE kind = ${decryptedEvent.kind}
|
||||
AND ccn_pubkey = ${ccnPubkey}
|
||||
AND (created_at < ${decryptedEvent.created_at} OR
|
||||
(created_at = ${decryptedEvent.created_at} AND id > ${decryptedEvent.id}))
|
||||
AND id IN (
|
||||
SELECT event_id FROM event_tags
|
||||
WHERE tag_name = 'd'
|
||||
AND tag_id IN (
|
||||
SELECT tag_id FROM event_tags_values
|
||||
WHERE value_position = 1
|
||||
AND value = ${dTag}
|
||||
)
|
||||
)
|
||||
`(db);
|
||||
} else {
|
||||
sql`
|
||||
UPDATE events
|
||||
SET replaced = 1
|
||||
WHERE kind = ${decryptedEvent.kind}
|
||||
AND ccn_pubkey = ${ccnPubkey}
|
||||
AND (created_at < ${decryptedEvent.created_at} OR
|
||||
(created_at = ${decryptedEvent.created_at} AND id > ${decryptedEvent.id}))
|
||||
`(db);
|
||||
}
|
||||
}
|
||||
|
||||
sql`
|
||||
INSERT INTO events (id, original_id, pubkey, created_at, kind, content, sig, first_seen, ccn_pubkey) VALUES (
|
||||
${decryptedEvent.id},
|
||||
${encryptedEvent.id},
|
||||
${decryptedEvent.pubkey},
|
||||
${decryptedEvent.created_at},
|
||||
${decryptedEvent.kind},
|
||||
${decryptedEvent.content},
|
||||
${decryptedEvent.sig},
|
||||
unixepoch(),
|
||||
${ccnPubkey}
|
||||
)
|
||||
`(db);
|
||||
log.debug('inserted event', { tag: 'addEventToDb', id: decryptedEvent.id });
|
||||
if (decryptedEvent.tags) {
|
||||
for (let i = 0; i < decryptedEvent.tags.length; i++) {
|
||||
const tag = sql`
|
||||
INSERT INTO event_tags(event_id, tag_name, tag_index) VALUES (
|
||||
${decryptedEvent.id},
|
||||
${decryptedEvent.tags[i][0]},
|
||||
${i}
|
||||
) RETURNING tag_id
|
||||
`(db)[0];
|
||||
for (let j = 1; j < decryptedEvent.tags[i].length; j++) {
|
||||
sql`
|
||||
INSERT INTO event_tags_values(tag_id, value_position, value) VALUES (
|
||||
${tag.tag_id},
|
||||
${j},
|
||||
${decryptedEvent.tags[i][j]}
|
||||
)
|
||||
`(db);
|
||||
}
|
||||
}
|
||||
log.debug('inserted tags for event', {
|
||||
tag: 'addEventToDb',
|
||||
id: decryptedEvent.id,
|
||||
});
|
||||
}
|
||||
db.run('COMMIT TRANSACTION');
|
||||
log.debug('committed transaction', { tag: 'addEventToDb' });
|
||||
} catch (e) {
|
||||
db.run('ROLLBACK TRANSACTION');
|
||||
log.error('transaction rolled back', { tag: 'addEventToDb', error: e });
|
||||
throw e;
|
||||
}
|
||||
log.debug('end', { tag: 'addEventToDb', id: decryptedEvent.id });
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue