@@ -13,7 +13,7 @@ import { identifyEvent, signEvent } from '../../../src/utils/event'
1313import { IncomingEventMessage , MessageType } from '../../../src/@types/messages'
1414import { CacheAdmissionState } from '../../../src/constants/caching'
1515import { Event } from '../../../src/@types/event'
16- import { EventKinds } from '../../../src/constants/base'
16+ import { EventKinds , EventExpirationTimeMetadataKey , EventTags } from '../../../src/constants/base'
1717import { EventMessageHandler } from '../../../src/handlers/event-message-handler'
1818import { IUserRepository } from '../../../src/@types/repositories'
1919import { IWebSocketAdapter } from '../../../src/@types/adapters'
@@ -172,6 +172,23 @@ describe('EventMessageHandler', () => {
172172 expect ( strategyFactoryStub ) . not . to . have . been . called
173173 } )
174174
175+ it ( 'rejects event if NIP-05 verification is required' , async ( ) => {
176+ canAcceptEventStub . returns ( undefined )
177+ isEventValidStub . resolves ( undefined )
178+ isUserAdmitted . resolves ( undefined )
179+ sandbox . stub ( EventMessageHandler . prototype , 'checkNip05Verification' as any ) . resolves ( 'blocked: NIP-05 verification required' )
180+
181+ await handler . handleMessage ( message )
182+
183+ expect ( onMessageSpy ) . to . have . been . calledOnceWithExactly ( [
184+ MessageType . OK ,
185+ event . id ,
186+ false ,
187+ 'blocked: NIP-05 verification required' ,
188+ ] )
189+ expect ( strategyFactoryStub ) . not . to . have . been . called
190+ } )
191+
175192 it ( 'rejects event if it is expired' , async ( ) => {
176193 isEventValidStub . resolves ( undefined )
177194
@@ -280,6 +297,14 @@ describe('EventMessageHandler', () => {
280297 } )
281298
282299 describe ( 'createdAt' , ( ) => {
300+ it ( 'returns undefined if event pubkey equals relay public key' , ( ) => {
301+ sandbox . stub ( EventMessageHandler . prototype , 'getRelayPublicKey' as any ) . returns ( event . pubkey )
302+ eventLimits . createdAt . maxPositiveDelta = 1
303+ event . created_at += 999
304+
305+ expect ( ( handler as any ) . canAcceptEvent ( event ) ) . to . be . undefined
306+ } )
307+
283308 describe ( 'maxPositiveDelta' , ( ) => {
284309 it ( 'returns undefined if maxPositiveDelta is zero' , ( ) => {
285310 eventLimits . createdAt . maxPositiveDelta = 0
@@ -291,9 +316,9 @@ describe('EventMessageHandler', () => {
291316 eventLimits . createdAt . maxPositiveDelta = 100
292317 event . created_at += 101
293318
294- expect ( ( handler as any ) . canAcceptEvent ( event ) ) . to . equal (
295- 'rejected: created_at is more than 100 seconds in the future' ,
296- )
319+ expect (
320+ ( handler as any ) . canAcceptEvent ( event )
321+ ) . to . equal ( 'rejected: created_at is more than 100 seconds in the future' )
297322 } )
298323 } )
299324
@@ -616,6 +641,22 @@ describe('EventMessageHandler', () => {
616641 }
617642 } )
618643
644+ it ( 'returns reason if request to vanish relay tag does not match relay URL' , async ( ) => {
645+ const privkey = '0000000000000000000000000000000000000000000000000000000000000001'
646+ const unsignedEvent = await identifyEvent ( {
647+ pubkey : '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' ,
648+ created_at : 1700000000 ,
649+ kind : EventKinds . REQUEST_TO_VANISH ,
650+ tags : [ [ EventTags . Relay , 'wss://another-relay.example' ] ] ,
651+ content : '' ,
652+ } )
653+ const vanishEvent = await signEvent ( privkey ) ( unsignedEvent )
654+
655+ return expect ( ( handler as any ) . isEventValid ( vanishEvent ) ) . to . eventually . equal (
656+ 'invalid: request to vanish relay tag invalid' ,
657+ )
658+ } )
659+
619660 it ( 'returns undefined if event is valid' , ( ) => {
620661 return expect ( ( handler as any ) . isEventValid ( event ) ) . to . eventually . be . undefined
621662 } )
@@ -683,6 +724,36 @@ describe('EventMessageHandler', () => {
683724 } )
684725 } )
685726
727+ describe ( 'isBlockedByRequestToVanish' , ( ) => {
728+ beforeEach ( ( ) => {
729+ handler = new EventMessageHandler (
730+ { } as any ,
731+ ( ) => null ,
732+ { } as any ,
733+ userRepository ,
734+ ( ) =>
735+ ( {
736+ info : { relay_url : 'relay_url' } ,
737+ } ) as any ,
738+ { } as any ,
739+ { hasKey : async ( ) => false , setKey : async ( ) => true } as any ,
740+ ( ) => ( { hit : async ( ) => false } ) ,
741+ )
742+ } )
743+
744+ it ( 'returns undefined for request to vanish events' , async ( ) => {
745+ event . kind = EventKinds . REQUEST_TO_VANISH
746+
747+ return expect ( ( handler as any ) . isBlockedByRequestToVanish ( event ) ) . to . eventually . be . undefined
748+ } )
749+
750+ it ( "returns undefined if event pubkey equals relay's own public key" , async ( ) => {
751+ sandbox . stub ( EventMessageHandler . prototype , 'getRelayPublicKey' as any ) . returns ( event . pubkey )
752+
753+ return expect ( ( handler as any ) . isBlockedByRequestToVanish ( event ) ) . to . eventually . be . undefined
754+ } )
755+ } )
756+
686757 describe ( 'isRateLimited' , ( ) => {
687758 let eventLimits : EventLimits
688759 let settings : Settings
@@ -743,6 +814,21 @@ describe('EventMessageHandler', () => {
743814 return expect ( ( handler as any ) . isRateLimited ( event ) ) . to . eventually . be . false
744815 } )
745816
817+ it ( "fulfills with false if event pubkey equals relay's own public key" , async ( ) => {
818+ sandbox . stub ( EventMessageHandler . prototype , 'getRelayPublicKey' as any ) . returns ( event . pubkey )
819+ eventLimits . rateLimits = [
820+ {
821+ period : 60000 ,
822+ rate : 1 ,
823+ } ,
824+ ]
825+
826+ const actualResult = await ( handler as any ) . isRateLimited ( event )
827+
828+ expect ( actualResult ) . to . be . false
829+ expect ( rateLimiterHitStub ) . not . to . have . been . called
830+ } )
831+
746832 it ( 'skips rate limiter if IP is whitelisted' , async ( ) => {
747833 eventLimits . rateLimits = [
748834 {
@@ -1098,6 +1184,17 @@ describe('EventMessageHandler', () => {
10981184 } )
10991185
11001186 describe ( 'caching' , ( ) => {
1187+ it ( 'falls back to repository lookup when cache read fails' , async ( ) => {
1188+ cacheStub . getKey . rejects ( new Error ( 'cache unavailable' ) )
1189+ settings . limits . event . pubkey . minBalance = 100n
1190+ userRepositoryFindByPubkeyStub . resolves ( { isAdmitted : true , balance : 150n } )
1191+
1192+ await expect ( ( handler as any ) . isUserAdmitted ( event ) ) . to . eventually . be . undefined
1193+
1194+ expect ( userRepositoryFindByPubkeyStub ) . to . have . been . calledOnceWithExactly ( event . pubkey )
1195+ expect ( cacheStub . setKey ) . to . have . been . calledWith ( `${ event . pubkey } :is-admitted` , CacheAdmissionState . ADMITTED , 300 )
1196+ } )
1197+
11011198 it ( 'fulfills with undefined and uses cache hit for admitted user without hitting DB' , async ( ) => {
11021199 cacheStub . getKey . resolves ( CacheAdmissionState . ADMITTED )
11031200
@@ -1341,6 +1438,35 @@ describe('EventMessageHandler', () => {
13411438 } )
13421439 } )
13431440
1441+ describe ( 'addExpirationMetadata' , ( ) => {
1442+ beforeEach ( ( ) => {
1443+ handler = new EventMessageHandler (
1444+ { } as any ,
1445+ ( ) => null ,
1446+ { } as any ,
1447+ userRepository ,
1448+ ( ) =>
1449+ ( {
1450+ info : { relay_url : 'relay_url' } ,
1451+ } ) as any ,
1452+ { } as any ,
1453+ { hasKey : async ( ) => false , setKey : async ( ) => true } as any ,
1454+ ( ) => ( { hit : async ( ) => false } ) ,
1455+ )
1456+ } )
1457+
1458+ it ( 'adds expiration metadata when expiration tag is present' , ( ) => {
1459+ const expiringEvent : Event = {
1460+ ...event ,
1461+ tags : [ [ EventTags . Expiration , '1665547000' ] ] ,
1462+ }
1463+
1464+ const enriched = ( handler as any ) . addExpirationMetadata ( expiringEvent )
1465+
1466+ expect ( ( enriched as any ) [ EventExpirationTimeMetadataKey ] ) . to . equal ( 1665547000 )
1467+ } )
1468+ } )
1469+
13441470 describe ( 'processNip05Metadata' , ( ) => {
13451471 let settings : Settings
13461472 let nip05VerificationRepository : any
@@ -1422,6 +1548,18 @@ describe('EventMessageHandler', () => {
14221548 expect ( verifyStub ) . not . to . have . been . called
14231549 } )
14241550
1551+ it ( 'ignores delete errors when kind-0 has no nip05 in content' , async ( ) => {
1552+ nip05VerificationRepository . deleteByPubkey . rejects ( new Error ( 'db down' ) )
1553+ event . kind = EventKinds . SET_METADATA
1554+ event . content = JSON . stringify ( { name : 'alice' } )
1555+
1556+ ; ( handler as any ) . processNip05Metadata ( event )
1557+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
1558+
1559+ expect ( nip05VerificationRepository . deleteByPubkey ) . to . have . been . calledOnceWithExactly ( event . pubkey )
1560+ expect ( verifyStub ) . not . to . have . been . called
1561+ } )
1562+
14251563 it ( 'does nothing when nip05 identifier is unparseable' , async ( ) => {
14261564 event . kind = EventKinds . SET_METADATA
14271565 event . content = JSON . stringify ( { nip05 : 'invalid-no-at-sign' } )
@@ -1969,4 +2107,4 @@ describe('EventMessageHandler', () => {
19692107 expect ( nip05VerificationRepository . upsert ) . to . have . been . calledOnce
19702108 } )
19712109 } )
1972- } )
2110+ } )
0 commit comments