diff --git a/resources/default-settings.yaml b/resources/default-settings.yaml
index a27ddd32..fac389c8 100755
--- a/resources/default-settings.yaml
+++ b/resources/default-settings.yaml
@@ -82,14 +82,14 @@ limits:
- 10
- - 40
- 49
- maxLength: 65536
+ maxLength: 102400
- description: 96 KB for event kind ranges 11-39 and 50-max
kinds:
- - 11
- 39
- - 50
- 9007199254740991
- maxLength: 98304
+ maxLength: 102400
rateLimits:
- description: 6 events/min for event kinds 0, 3, 40 and 41
kinds:
@@ -143,6 +143,10 @@ limits:
subscription:
maxSubscriptions: 10
maxFilters: 10
+ maxFilterValues: 2500
+ maxSubscriptionIdLength: 256
+ maxLimit: 5000
+ minPrefixLength: 4
message:
rateLimits:
- description: 240 raw messages/min
diff --git a/resources/invoices.html b/resources/invoices.html
index 48ceca3c..7682a0cb 100644
--- a/resources/invoices.html
+++ b/resources/invoices.html
@@ -107,8 +107,11 @@
Invoice expired!
var timeout
var paid = false
var fallbackTimeout
+ var now = Math.floor(Date.now()/1000)
console.log('invoice id', reference)
+ console.log('pubkey', pubkey)
+ console.log('bolt11', invoice)
function getBackoffTime() {
return 5000 + Math.floor(Math.random() * 5000)
@@ -149,7 +152,8 @@ Invoice expired!
var socket = new WebSocket(relayUrl)
socket.onopen = () => {
console.log('connected')
- socket.send(JSON.stringify(['REQ', 'payment', { kinds: [4], authors: [relayPubkey], '#c': [reference], limit: 1 }]))
+ var subscription = ['REQ', 'payment', { kinds: [402], '#p': [pubkey], since: now - 60 }]
+ socket.send(JSON.stringify(subscription))
}
socket.onmessage = (raw) => {
@@ -162,16 +166,22 @@ Invoice expired!
switch (message[0]) {
case 'EVENT': {
- // TODO: validate event
const event = message[2]
- // TODO: validate signature
- if (event.pubkey === relayPubkey) {
- paid = true
+ if (
+ event.pubkey === relayPubkey
+ && event.kind === 402
+ ) {
+ const pubkeyTag = event.tags.find((t) => t[0] === 'p' && t[1] === pubkey)
+ const invoiceTag = event.tags.find((t) => t[0] === 'bolt11' && t[1] === invoice)
- if (expiresAt) clearTimeout(timeout)
+ if (pubkeyTag && invoiceTag) {
+ paid = true
- hide('pending')
- show('paid')
+ if (expiresAt) clearTimeout(timeout)
+
+ hide('pending')
+ show('paid')
+ }
}
}
break;
diff --git a/src/@types/base.ts b/src/@types/base.ts
index 7065832b..78808cf5 100644
--- a/src/@types/base.ts
+++ b/src/@types/base.ts
@@ -1,17 +1,23 @@
import { Knex } from 'knex'
import { SocketAddress } from 'net'
+import { EventTags } from '../constants/base'
+
export type EventId = string
export type Pubkey = string
-export type TagName = string
+export type TagName = EventTags | string
export type Signature = string
export type Tag = TagBase & string[]
export type Secret = string
-export interface TagBase {
- 0: TagName
- [index: number]: string
+type ExtraTagValues = {
+ [index in Range<2, 100>]?: string
+}
+
+export interface TagBase extends ExtraTagValues {
+ 0: TagName;
+ 1: string
}
type Enumerate<
diff --git a/src/@types/services.ts b/src/@types/services.ts
index 8058f5ae..9a151375 100644
--- a/src/@types/services.ts
+++ b/src/@types/services.ts
@@ -13,7 +13,6 @@ export interface IPaymentsService {
confirmInvoice(
invoice: Pick,
): Promise
- sendNewInvoiceNotification(invoice: Invoice): Promise
sendInvoiceUpdateNotification(invoice: Invoice): Promise
getPendingInvoices(): Promise
}
diff --git a/src/@types/settings.ts b/src/@types/settings.ts
index a0ddec7c..c7920d93 100644
--- a/src/@types/settings.ts
+++ b/src/@types/settings.ts
@@ -82,6 +82,10 @@ export interface EventLimits {
export interface ClientSubscriptionLimits {
maxSubscriptions?: number
maxFilters?: number
+ maxFilterValues?: number
+ maxLimit?: number
+ minPrefixLength?: number
+ maxSubscriptionIdLength?: number
}
export interface ClientLimits {
diff --git a/src/app/maintenance-worker.ts b/src/app/maintenance-worker.ts
index 4570a072..4c929c8f 100644
--- a/src/app/maintenance-worker.ts
+++ b/src/app/maintenance-worker.ts
@@ -1,5 +1,5 @@
+import { mergeDeepLeft, path, pipe } from 'ramda'
import { IRunnable } from '../@types/base'
-import { path } from 'ramda'
import { createLogger } from '../factories/logger-factory'
import { delayMs } from '../utils/misc'
@@ -47,21 +47,27 @@ export class MaintenanceWorker implements IRunnable {
for (const invoice of invoices) {
debug('invoice %s: %o', invoice.id, invoice)
try {
- debug('getting invoice %s from payment processor', invoice.id)
+ debug('getting invoice %s from payment processor: %o', invoice.id, invoice)
const updatedInvoice = await this.paymentsService.getInvoiceFromPaymentsProcessor(invoice)
await delay()
- debug('updating invoice status %s: %o', invoice.id, invoice)
+ debug('updating invoice status %s: %o', updatedInvoice.id, updatedInvoice)
await this.paymentsService.updateInvoiceStatus(updatedInvoice)
if (
invoice.status !== updatedInvoice.status
&& updatedInvoice.status == InvoiceStatus.COMPLETED
- && invoice.confirmedAt
+ && updatedInvoice.confirmedAt
) {
debug('confirming invoice %s & notifying %s', invoice.id, invoice.pubkey)
+
+ const update = pipe(
+ mergeDeepLeft(updatedInvoice),
+ mergeDeepLeft({ amountPaid: invoice.amountRequested }),
+ )(invoice)
+
await Promise.all([
- this.paymentsService.confirmInvoice(invoice),
- this.paymentsService.sendInvoiceUpdateNotification(invoice),
+ this.paymentsService.confirmInvoice(update),
+ this.paymentsService.sendInvoiceUpdateNotification(update),
])
await delay()
diff --git a/src/constants/base.ts b/src/constants/base.ts
index e23f4bb6..37bce50e 100644
--- a/src/constants/base.ts
+++ b/src/constants/base.ts
@@ -27,7 +27,6 @@ export enum EventKinds {
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
PARAMETERIZED_REPLACEABLE_LAST = 39999,
USER_APPLICATION_FIRST = 40000,
- USER_APPLICATION_LAST = Number.MAX_SAFE_INTEGER,
}
export enum EventTags {
@@ -37,6 +36,7 @@ export enum EventTags {
Delegation = 'delegation',
Deduplication = 'd',
Expiration = 'expiration',
+ Invoice = 'bolt11',
}
export enum PaymentsProcessors {
diff --git a/src/controllers/invoices/post-invoice-controller.ts b/src/controllers/invoices/post-invoice-controller.ts
index 646ba85e..7fe3a027 100644
--- a/src/controllers/invoices/post-invoice-controller.ts
+++ b/src/controllers/invoices/post-invoice-controller.ts
@@ -136,7 +136,12 @@ export class PostInvoiceController implements IController {
}
let invoice: Invoice
- const amount = admissionFee.reduce((sum, fee) => sum + BigInt(fee.amount), 0n)
+ const amount = admissionFee.reduce((sum, fee) => {
+ return fee.enabled && !fee.whitelists?.pubkeys?.includes(pubkey)
+ ? BigInt(fee.amount) + sum
+ : sum
+ }, 0n)
+
try {
const description = `${relayName} Admission Fee for ${toBech32('npub')(pubkey)}`
@@ -145,8 +150,6 @@ export class PostInvoiceController implements IController {
amount,
description,
)
-
- await this.paymentsService.sendNewInvoiceNotification(invoice)
} catch (error) {
console.error('Unable to create invoice. Reason:', error)
response
diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts
index bbea8775..07264656 100644
--- a/src/handlers/event-message-handler.ts
+++ b/src/handlers/event-message-handler.ts
@@ -1,6 +1,6 @@
import { Event, ExpiringEvent } from '../@types/event'
import { EventRateLimit, FeeSchedule, Settings } from '../@types/settings'
-import { getEventExpiration, getEventProofOfWork, getPubkeyProofOfWork, isEventIdValid, isEventKindOrRangeMatch, isEventSignatureValid, isExpiredEvent } from '../utils/event'
+import { getEventExpiration, getEventProofOfWork, getPubkeyProofOfWork, getPublicKey, getRelayPrivateKey, isEventIdValid, isEventKindOrRangeMatch, isEventSignatureValid, isExpiredEvent } from '../utils/event'
import { IEventStrategy, IMessageHandler } from '../@types/message-handlers'
import { ContextMetadataKey } from '../constants/base'
import { createCommandResult } from '../utils/messages'
@@ -79,7 +79,15 @@ export class EventMessageHandler implements IMessageHandler {
}
}
+ protected getRelayPublicKey(): string {
+ const relayPrivkey = getRelayPrivateKey(this.settings().info.relay_url)
+ return getPublicKey(relayPrivkey)
+ }
+
protected canAcceptEvent(event: Event): string | undefined {
+ if (this.getRelayPublicKey() === event.pubkey) {
+ return
+ }
const now = Math.floor(Date.now()/1000)
const limits = this.settings().limits?.event ?? {}
@@ -185,6 +193,10 @@ export class EventMessageHandler implements IMessageHandler {
}
protected async isRateLimited(event: Event): Promise {
+ if (this.getRelayPublicKey() === event.pubkey) {
+ return false
+ }
+
const { whitelists, rateLimits } = this.settings().limits?.event ?? {}
if (!rateLimits || !rateLimits.length) {
return false
@@ -249,6 +261,10 @@ export class EventMessageHandler implements IMessageHandler {
return
}
+ if (this.getRelayPublicKey() === event.pubkey) {
+ return
+ }
+
const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled
&& !feeSchedule.whitelists?.pubkeys?.some((prefix) => event.pubkey.startsWith(prefix))
diff --git a/src/handlers/request-handlers/root-request-handler.ts b/src/handlers/request-handlers/root-request-handler.ts
index dc0073b1..d9450334 100644
--- a/src/handlers/request-handlers/root-request-handler.ts
+++ b/src/handlers/request-handlers/root-request-handler.ts
@@ -17,6 +17,8 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
paymentsUrl.protocol = paymentsUrl.protocol === 'wss:' ? 'https:' : 'http:'
paymentsUrl.pathname = '/invoices'
+ const content = settings.limits?.event?.content
+
const relayInformationDocument = {
name,
description,
@@ -29,12 +31,14 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
limitation: {
max_message_length: settings.network.maxPayloadSize,
max_subscriptions: settings.limits?.client?.subscription?.maxSubscriptions,
- max_filters: settings.limits?.client?.subscription?.maxFilters,
- max_limit: 5000,
- max_subid_length: 256,
- min_prefix: 4,
+ max_filters: settings.limits?.client?.subscription?.maxFilterValues,
+ max_limit: settings.limits?.client?.subscription?.maxLimit,
+ max_subid_length: settings.limits?.client?.subscription?.maxSubscriptionIdLength,
+ min_prefix: settings.limits?.client?.subscription?.minPrefixLength,
max_event_tags: 2500,
- max_content_length: 102400,
+ max_content_length: Array.isArray(content)
+ ? content[0].maxLength // best guess since we have per-kind limits
+ : content?.maxLength,
min_pow_difficulty: settings.limits?.event?.eventId?.minLeadingZeroBits,
auth_required: false,
payment_required: settings.payments?.enabled,
diff --git a/src/handlers/subscribe-message-handler.ts b/src/handlers/subscribe-message-handler.ts
index abb47ec3..f85c2a5f 100644
--- a/src/handlers/subscribe-message-handler.ts
+++ b/src/handlers/subscribe-message-handler.ts
@@ -87,23 +87,33 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
private canSubscribe(subscriptionId: SubscriptionId, filters: SubscriptionFilter[]): string | undefined {
const subscriptions = this.webSocket.getSubscriptions()
const existingSubscription = subscriptions.get(subscriptionId)
+ const subscriptionLimits = this.settings().limits?.client?.subscription
if (existingSubscription?.length && equals(filters, existingSubscription)) {
return `Duplicate subscription ${subscriptionId}: Ignorning`
}
- const maxSubscriptions = this.settings().limits?.client?.subscription?.maxSubscriptions ?? 0
+ const maxSubscriptions = subscriptionLimits?.maxSubscriptions ?? 0
if (maxSubscriptions > 0
&& !existingSubscription?.length && subscriptions.size + 1 > maxSubscriptions
) {
return `Too many subscriptions: Number of subscriptions must be less than or equal to ${maxSubscriptions}`
}
- const maxFilters = this.settings().limits?.client?.subscription?.maxFilters ?? 0
+ const maxFilters = subscriptionLimits?.maxFilters ?? 0
if (maxFilters > 0) {
if (filters.length > maxFilters) {
return `Too many filters: Number of filters per susbscription must be less then or equal to ${maxFilters}`
}
}
+
+ if (
+ typeof subscriptionLimits.maxSubscriptionIdLength === 'number'
+ && subscriptionId.length > subscriptionLimits.maxSubscriptionIdLength
+ ) {
+ return `Subscription ID too long: Subscription ID must be less or equal to ${subscriptionLimits.maxSubscriptionIdLength}`
+ }
+
+
}
}
diff --git a/src/payments-processors/lnurl-payments-processor.ts b/src/payments-processors/lnurl-payments-processor.ts
index 7c0b275c..30b412e3 100644
--- a/src/payments-processors/lnurl-payments-processor.ts
+++ b/src/payments-processors/lnurl-payments-processor.ts
@@ -23,7 +23,8 @@ export class LnurlPaymentsProcesor implements IPaymentsProcessor {
return {
id: invoice.id,
- status: response.data.settled ? InvoiceStatus['COMPLETED'] : InvoiceStatus['PENDING'],
+ confirmedAt: response.data.settled ? new Date() : undefined,
+ status: response.data.settled ? InvoiceStatus.COMPLETED : InvoiceStatus.PENDING,
}
} catch (error) {
console.error(`Unable to get invoice ${invoice.id}. Reason:`, error)
diff --git a/src/schemas/base-schema.ts b/src/schemas/base-schema.ts
index 334ebdd8..fe24136f 100644
--- a/src/schemas/base-schema.ts
+++ b/src/schemas/base-schema.ts
@@ -10,7 +10,7 @@ export const kindSchema = Schema.number().min(0).multiple(1).label('kind')
export const signatureSchema = Schema.string().case('lower').hex().length(128).label('sig')
-export const subscriptionSchema = Schema.string().min(1).max(255).label('subscriptionId')
+export const subscriptionSchema = Schema.string().min(1).label('subscriptionId')
const seconds = (value: any, helpers: any) => (Number.isSafeInteger(value) && Math.log10(value) < 10) ? value : helpers.error('any.invalid')
@@ -20,5 +20,4 @@ export const createdAtSchema = Schema.number().min(0).multiple(1).custom(seconds
export const tagSchema = Schema.array()
.ordered(Schema.string().max(255).required().label('identifier'))
.items(Schema.string().allow('').max(1024).label('value'))
- .max(10)
.label('tag')
diff --git a/src/schemas/event-schema.ts b/src/schemas/event-schema.ts
index 10141020..fab6520b 100644
--- a/src/schemas/event-schema.ts
+++ b/src/schemas/event-schema.ts
@@ -31,10 +31,9 @@ export const eventSchema = Schema.object({
pubkey: pubkeySchema.required(),
created_at: createdAtSchema.required(),
kind: kindSchema.required(),
- tags: Schema.array().items(tagSchema).max(2500).required(),
+ tags: Schema.array().items(tagSchema).required(),
content: Schema.string()
.allow('')
- .max(100 * 1024) // 100 kB
.required(),
sig: signatureSchema.required(),
}).unknown(false)
diff --git a/src/schemas/filter-schema.ts b/src/schemas/filter-schema.ts
index 1fd2fed9..f67453f2 100644
--- a/src/schemas/filter-schema.ts
+++ b/src/schemas/filter-schema.ts
@@ -3,10 +3,10 @@ import Schema from 'joi'
import { createdAtSchema, kindSchema, prefixSchema } from './base-schema'
export const filterSchema = Schema.object({
- ids: Schema.array().items(prefixSchema.label('prefixOrId')).max(1000),
- authors: Schema.array().items(prefixSchema.label('prefixOrAuthor')).max(1000),
- kinds: Schema.array().items(kindSchema).max(20),
+ ids: Schema.array().items(prefixSchema.label('prefixOrId')),
+ authors: Schema.array().items(prefixSchema.label('prefixOrAuthor')),
+ kinds: Schema.array().items(kindSchema),
since: createdAtSchema,
until: createdAtSchema,
- limit: Schema.number().min(0).multiple(1).max(5000),
-}).pattern(/^#[a-z]$/, Schema.array().items(Schema.string().max(1024)).max(256))
+ limit: Schema.number().min(0).multiple(1),
+}).pattern(/^#[a-z]$/, Schema.array().items(Schema.string().max(1024)))
diff --git a/src/services/payments-service.ts b/src/services/payments-service.ts
index 7e03e067..312b5b0d 100644
--- a/src/services/payments-service.ts
+++ b/src/services/payments-service.ts
@@ -1,17 +1,16 @@
-import { andThen, pipe } from 'ramda'
-import { broadcastEvent, encryptKind4Event, getPublicKey, getRelayPrivateKey, identifyEvent, signEvent } from '../utils/event'
+import { andThen, otherwise, pipe } from 'ramda'
+import { broadcastEvent, getPublicKey, getRelayPrivateKey, identifyEvent, signEvent } from '../utils/event'
import { DatabaseClient, Pubkey } from '../@types/base'
import { FeeSchedule, Settings } from '../@types/settings'
import { IEventRepository, IInvoiceRepository, IUserRepository } from '../@types/repositories'
import { Invoice, InvoiceStatus, InvoiceUnit } from '../@types/invoice'
+import { Event, ExpiringEvent, UnidentifiedEvent } from '../@types/event'
+import { EventExpirationTimeMetadataKey, EventKinds, EventTags } from '../constants/base'
import { createLogger } from '../factories/logger-factory'
-import { EventKinds } from '../constants/base'
import { IPaymentsProcessor } from '../@types/clients'
import { IPaymentsService } from '../@types/services'
-import { toBech32 } from '../utils/transform'
import { Transaction } from '../database/transaction'
-import { UnidentifiedEvent } from '../@types/event'
const debug = createLogger('payments-service')
@@ -54,7 +53,7 @@ export class PaymentsService implements IPaymentsService {
amount: bigint,
description: string,
): Promise {
- debug('create invoice for %s for %s: %d', pubkey, amount.toString(), description)
+ debug('create invoice for %s for %s: %s', pubkey, amount.toString(), description)
const transaction = new Transaction(this.dbClient)
try {
@@ -220,68 +219,6 @@ export class PaymentsService implements IPaymentsService {
}
}
- public async sendNewInvoiceNotification(invoice: Invoice): Promise {
- debug('invoice created notification %s: %o', invoice.id, invoice)
- const currentSettings = this.settings()
-
- const {
- info: {
- relay_url: relayUrl,
- name: relayName,
- },
- } = currentSettings
-
- const relayPrivkey = getRelayPrivateKey(relayUrl)
- const relayPubkey = getPublicKey(relayPrivkey)
-
- let unit: string = invoice.unit
- let amount: bigint = invoice.amountRequested
- if (invoice.unit === InvoiceUnit.MSATS) {
- amount /= 1000n
- unit = 'sats'
- }
-
- const url = new URL(relayUrl)
-
- const terms = new URL(relayUrl)
- terms.protocol = ['https', 'wss'].includes(url.protocol)
- ? 'https'
- : 'http'
- terms.pathname += 'terms'
-
- const unsignedInvoiceEvent: UnidentifiedEvent = {
- pubkey: relayPubkey,
- kind: EventKinds.ENCRYPTED_DIRECT_MESSAGE,
- created_at: Math.floor(invoice.createdAt.getTime() / 1000),
- content: `From: ${toBech32('npub')(relayPubkey)}@${url.hostname} (${relayName})
-To: ${toBech32('npub')(invoice.pubkey)}@${url.hostname}
-🧾 Admission Fee Invoice
-
-Amount: ${amount.toString()} ${unit}
-
-⚠️ By paying this invoice, you confirm that you have read and agree to the Terms of Service:
-${terms.toString()}
-${invoice.expiresAt ? `
-⏳ Expires at ${invoice.expiresAt.toISOString()}` : ''}
-
-${invoice.bolt11}`,
- tags: [
- ['p', invoice.pubkey],
- ['bolt11', invoice.bolt11],
- ],
- }
-
- const persistEvent = this.eventRepository.create.bind(this.eventRepository)
-
- await pipe(
- identifyEvent,
- andThen(encryptKind4Event(relayPrivkey, invoice.pubkey)),
- andThen(signEvent(relayPrivkey)),
- andThen(broadcastEvent),
- andThen(persistEvent),
- )(unsignedInvoiceEvent)
- }
-
public async sendInvoiceUpdateNotification(invoice: Invoice): Promise {
debug('invoice updated notification %s: %o', invoice.id, invoice)
const currentSettings = this.settings()
@@ -289,7 +226,6 @@ ${invoice.bolt11}`,
const {
info: {
relay_url: relayUrl,
- name: relayName,
},
} = currentSettings
@@ -309,31 +245,36 @@ ${invoice.bolt11}`,
unit = InvoiceUnit.SATS
}
- const url = new URL(relayUrl)
+ const now = new Date()
+ const expiration = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate())
- const unsignedInvoiceEvent: UnidentifiedEvent = {
+ const unsignedInvoiceEvent: UnidentifiedEvent & Pick = {
pubkey: relayPubkey,
- kind: EventKinds.ENCRYPTED_DIRECT_MESSAGE,
- created_at: Math.floor(invoice.createdAt.getTime() / 1000),
- content: `🧾 Admission Fee Invoice Paid for ${relayPubkey}@${url.hostname} (${relayName})
-
-Amount received: ${amount.toString()} ${unit}
-
-Thanks!`,
+ kind: EventKinds.INVOICE_UPDATE,
+ created_at: Math.floor(now.getTime() / 1000),
+ content: `Invoice paid: ${amount.toString()} ${unit}`,
tags: [
- ['p', invoice.pubkey],
- ['c', invoice.id],
+ [EventTags.Pubkey, invoice.pubkey],
+ [EventTags.Invoice, invoice.bolt11],
+ [EventTags.Expiration, Math.floor(expiration.getTime() / 1000).toString()],
],
+ [EventExpirationTimeMetadataKey]: expiration.getTime() / 1000,
}
- const persistEvent = this.eventRepository.create.bind(this.eventRepository)
+ const persistEvent = async (event: Event) => {
+ await this.eventRepository.create(event)
+
+ return event
+ }
+
+ const logError = (error: Error) => console.error('Unable to send notification', error)
await pipe(
identifyEvent,
- andThen(encryptKind4Event(relayPrivkey, invoice.pubkey)),
andThen(signEvent(relayPrivkey)),
- andThen(broadcastEvent),
andThen(persistEvent),
+ andThen(broadcastEvent),
+ otherwise(logError),
)(unsignedInvoiceEvent)
}
}
diff --git a/src/utils/event.ts b/src/utils/event.ts
index 577bb478..cf069d6d 100644
--- a/src/utils/event.ts
+++ b/src/utils/event.ts
@@ -181,15 +181,33 @@ export const identifyEvent = async (event: UnidentifiedEvent): Promise Buffer.from(secp256k1.getPublicKey(privkey, true)).subarray(1).toString('hex')
+const publicKeyCache: Record = {}
+export const getPublicKey = (privkey: string) => {
+ if (privkey in publicKeyCache) {
+ return publicKeyCache[privkey]
+ }
+
+ publicKeyCache[privkey] = secp256k1.utils.bytesToHex(secp256k1.getPublicKey(privkey, true).subarray(1))
+
+ return publicKeyCache[privkey]
+}
export const signEvent = (privkey: string | Buffer | undefined) => async (event: UnsignedEvent): Promise => {
const sig = await secp256k1.schnorr.sign(event.id, privkey as any)
diff --git a/src/utils/secret.ts b/src/utils/secret.ts
index 4260ec99..16b7fd28 100644
--- a/src/utils/secret.ts
+++ b/src/utils/secret.ts
@@ -1,11 +1,15 @@
import { createHmac } from 'crypto'
export function deriveFromSecret(purpose: string | Buffer): Buffer {
- return hmacSha256(process.env.SECRET as string, purpose)
+ if (!process.env.SECRET) {
+ throw new Error('SECRET environment variable not set')
+ }
+
+ return hmacSha256(process.env.SECRET, purpose)
}
export function hmacSha256(secret: string | Buffer, data: string | Buffer): Buffer {
- return createHmac('sha256', secret)
- .update(data)
- .digest()
+ return createHmac('sha256', secret)
+ .update(data)
+ .digest()
}
diff --git a/src/utils/sliding-window-rate-limiter.ts b/src/utils/sliding-window-rate-limiter.ts
index fdc3fdf6..7dcb62a9 100644
--- a/src/utils/sliding-window-rate-limiter.ts
+++ b/src/utils/sliding-window-rate-limiter.ts
@@ -17,8 +17,6 @@ export class SlidingWindowRateLimiter implements IRateLimiter {
const timestamp = Date.now()
const { period } = options
- debug('add %d hits on %s bucket', step, key)
-
const [,, entries] = await Promise.all([
this.cache.removeRangeByScoreFromSortedSet(key, 0, timestamp - period),
this.cache.addToSortedSet(key, { [`${timestamp}:${step}`]: timestamp.toString() }),
diff --git a/test/integration/features/helpers.ts b/test/integration/features/helpers.ts
index 26a2e379..34dc1653 100644
--- a/test/integration/features/helpers.ts
+++ b/test/integration/features/helpers.ts
@@ -54,7 +54,7 @@ export async function createEvent(input: Partial, privkey: any): Promise<
}
export function createIdentity(name: string) {
- const hmac = createHmac('sha256', process.env.SECRET ?? Math.random().toString())
+ const hmac = createHmac('sha256', Math.random().toString())
hmac.update(name)
const privkey = hmac.digest().toString('hex')
const pubkey = Buffer.from(secp256k1.getPublicKey(privkey, true)).toString('hex').substring(2)
diff --git a/test/integration/features/nip-01/nip-01.feature b/test/integration/features/nip-01/nip-01.feature
index e8d7264a..be0c2ca9 100644
--- a/test/integration/features/nip-01/nip-01.feature
+++ b/test/integration/features/nip-01/nip-01.feature
@@ -72,7 +72,6 @@ Feature: NIP-01
And Alice subscribes to text_note events from Bob and set_metadata events from Charlie
Then Alice receives 2 events from Bob and Charlie
- @test
Scenario: Alice is interested in Bob's events from back in November
Given someone called Alice
And someone called Bob
diff --git a/test/integration/features/nip-16/nip-16.feature b/test/integration/features/nip-16/nip-16.feature
index 53225b41..780e7ee7 100644
--- a/test/integration/features/nip-16/nip-16.feature
+++ b/test/integration/features/nip-16/nip-16.feature
@@ -12,9 +12,10 @@ Feature: NIP-16 Event treatment
Scenario: Charlie sends an ephemeral event
Given someone called Charlie
- And Charlie subscribes to author Charlie
+ Given someone called Alice
+ And Alice subscribes to author Charlie
When Charlie sends a ephemeral_event_0 event with content "now you see me"
- Then Charlie receives a ephemeral_event_0 event from Charlie with content "now you see me"
- Then Charlie unsubscribes from author Charlie
- When Charlie subscribes to author Charlie
- Then Charlie receives 0 ephemeral_event_0 events and EOSE
+ Then Alice receives a ephemeral_event_0 event from Charlie with content "now you see me"
+ Then Alice unsubscribes from author Charlie
+ When Alice subscribes to author Charlie
+ Then Alice receives 0 ephemeral_event_0 events and EOSE
diff --git a/test/integration/features/shared.ts b/test/integration/features/shared.ts
index 38aaa854..83281d05 100644
--- a/test/integration/features/shared.ts
+++ b/test/integration/features/shared.ts
@@ -35,6 +35,7 @@ export const streams = new WeakMap>()
BeforeAll({ timeout: 1000 }, async function () {
process.env.RELAY_PORT = '18808'
+ process.env.SECRET = Math.random().toString().repeat(6)
cacheClient = getCacheClient()
dbClient = getMasterDbClient()
rrDbClient = getReadReplicaDbClient()
@@ -43,11 +44,12 @@ BeforeAll({ timeout: 1000 }, async function () {
const settings = SettingsStatic.createSettings()
SettingsStatic._settings = pipe(
- assocPath( ['limits', 'event', 'createdAt', 'maxPositiveDelta'], 0),
- assocPath( ['limits', 'message', 'rateLimits'], []),
- assocPath( ['limits', 'event', 'rateLimits'], []),
- assocPath( ['limits', 'invoice', 'rateLimits'], []),
- assocPath( ['limits', 'connection', 'rateLimits'], []),
+ assocPath(['payments', 'enabled'], false),
+ assocPath(['limits', 'event', 'createdAt', 'maxPositiveDelta'], 0),
+ assocPath(['limits', 'message', 'rateLimits'], []),
+ assocPath(['limits', 'event', 'rateLimits'], []),
+ assocPath(['limits', 'invoice', 'rateLimits'], []),
+ assocPath(['limits', 'connection', 'rateLimits'], []),
)(settings) as any
worker = workerFactory()
@@ -80,11 +82,10 @@ After(async function () {
const dbClient = getMasterDbClient()
await dbClient('events')
- .where({
- event_pubkey: Object
+ .whereIn('event_pubkey', Object
.values(this.parameters.identities as Record)
.map(({ pubkey }) => Buffer.from(pubkey, 'hex')),
- }).del()
+ ).delete()
this.parameters.identities = {}
})
@@ -94,14 +95,14 @@ Given(/someone called (\w+)/, async function(name: string) {
this.parameters.clients[name] = connection
this.parameters.subscriptions[name] = []
this.parameters.events[name] = []
- const subject = new Subject()
- connection.once('close', subject.next.bind(subject))
+ const close = new Subject()
+ connection.once('close', close.next.bind(close))
- const project = (raw: MessageEvent) => JSON.parse(raw.data.toString('utf8'))
+ const projection = (raw: MessageEvent) => JSON.parse(raw.data.toString('utf8'))
const replaySubject = new ReplaySubject(2, 1000)
- fromEvent(connection, 'message').pipe(map(project) as any,takeUntil(subject)).subscribe(replaySubject)
+ fromEvent(connection, 'message').pipe(map(projection) as any,takeUntil(close)).subscribe(replaySubject)
streams.set(
connection,
diff --git a/test/unit/handlers/delegated-event-message-handler.spec.ts b/test/unit/handlers/delegated-event-message-handler.spec.ts
index d32e9ac6..a965bbe8 100644
--- a/test/unit/handlers/delegated-event-message-handler.spec.ts
+++ b/test/unit/handlers/delegated-event-message-handler.spec.ts
@@ -11,6 +11,7 @@ import { IncomingEventMessage, MessageType } from '../../../src/@types/messages'
import { DelegatedEventMessageHandler } from '../../../src/handlers/delegated-event-message-handler'
import { Event } from '../../../src/@types/event'
import { EventMessageHandler } from '../../../src/handlers/event-message-handler'
+import { EventTags } from '../../../src/constants/base'
import { IUserRepository } from '../../../src/@types/repositories'
import { WebSocketAdapterEvent } from '../../../src/constants/adapter'
@@ -38,7 +39,7 @@ describe('DelegatedEventMessageHandler', () => {
pubkey: 'f'.repeat(64),
sig: 'f'.repeat(128),
tags: [
- ['delegation', 'delegator', 'rune', 'signature'],
+ [EventTags.Delegation, 'delegator', 'rune', 'signature'],
],
}
})
@@ -192,7 +193,7 @@ describe('DelegatedEventMessageHandler', () => {
'kind': 1,
'tags': [
[
- 'delegation',
+ EventTags.Delegation,
'86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e',
'kind=1&created_at>1640995200',
'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1',
diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts
index a1e9004e..9cd73788 100644
--- a/test/unit/handlers/event-message-handler.spec.ts
+++ b/test/unit/handlers/event-message-handler.spec.ts
@@ -26,11 +26,17 @@ describe('EventMessageHandler', () => {
let event: Event
let message: IncomingEventMessage
let sandbox: Sinon.SinonSandbox
+ let origEnv: NodeJS.ProcessEnv
let originalConsoleWarn: (message?: any, ...optionalParams: any[]) => void | undefined = undefined
beforeEach(() => {
sandbox = Sinon.createSandbox()
+ origEnv = { ...process.env }
+ process.env = {
+ // deepcode ignore HardcodedNonCryptoSecret/test:
+ SECRET: 'changeme',
+ }
originalConsoleWarn = console.warn
console.warn = () => undefined
event = {
@@ -45,6 +51,7 @@ describe('EventMessageHandler', () => {
})
afterEach(() => {
+ process.env = origEnv
console.warn = originalConsoleWarn
sandbox.restore()
})
@@ -75,7 +82,9 @@ describe('EventMessageHandler', () => {
webSocket as any,
strategyFactoryStub,
userRepository,
- () => ({}) as any,
+ () => ({
+ info: { relay_url: 'relay_url' },
+ }) as any,
() => ({ hit: async () => false })
)
})
@@ -128,7 +137,7 @@ describe('EventMessageHandler', () => {
expect(isUserAdmitted).to.have.been.calledWithExactly(event)
expect(strategyFactoryStub).not.to.have.been.called
})
-
+
it('rejects event if it is expired', async () => {
isEventValidStub.resolves(undefined)
@@ -223,6 +232,9 @@ describe('EventMessageHandler', () => {
},
}
settings = {
+ info: {
+ relay_url: 'relay_url',
+ },
limits: {
event: eventLimits,
},
@@ -690,6 +702,9 @@ describe('EventMessageHandler', () => {
rateLimits: [],
}
settings = {
+ info: {
+ relay_url: 'relay_url',
+ },
limits: {
event: eventLimits,
},
diff --git a/test/unit/repositories/event-repository.spec.ts b/test/unit/repositories/event-repository.spec.ts
index d61006de..ea925066 100644
--- a/test/unit/repositories/event-repository.spec.ts
+++ b/test/unit/repositories/event-repository.spec.ts
@@ -11,7 +11,7 @@ chai.use(sinonChai)
const { expect } = chai
-import { ContextMetadataKey, EventDeduplicationMetadataKey } from '../../../src/constants/base'
+import { ContextMetadataKey, EventDeduplicationMetadataKey, EventTags } from '../../../src/constants/base'
import { DatabaseClient } from '../../../src/@types/base'
import { EventRepository } from '../../../src/repositories/event-repository'
@@ -383,12 +383,12 @@ describe('EventRepository', () => {
kind: 1,
tags: [
[
- 'p',
+ EventTags.Pubkey,
'8355095016fddbe31fcf1453b26f613553e9758cf2263e190eac8fd96a3d3de9',
'wss://nostr-pub.wellorder.net',
],
[
- 'e',
+ EventTags.Event,
'7377fa81fc6c7ae7f7f4ef8938d4a603f7bf98183b35ab128235cc92d4bebf96',
'wss://nostr-relay.untethr.me',
],
@@ -417,12 +417,12 @@ describe('EventRepository', () => {
kind: 1,
tags: [
[
- 'p',
+ EventTags.Pubkey,
'8355095016fddbe31fcf1453b26f613553e9758cf2263e190eac8fd96a3d3de9',
'wss://nostr-pub.wellorder.net',
],
[
- 'e',
+ EventTags.Event,
'7377fa81fc6c7ae7f7f4ef8938d4a603f7bf98183b35ab128235cc92d4bebf96',
'wss://nostr-relay.untethr.me',
],
diff --git a/test/unit/schemas/event-schema.spec.ts b/test/unit/schemas/event-schema.spec.ts
index 19426e9b..deae9629 100644
--- a/test/unit/schemas/event-schema.spec.ts
+++ b/test/unit/schemas/event-schema.spec.ts
@@ -1,8 +1,9 @@
-import { assocPath, omit, range } from 'ramda'
+import { assocPath, omit } from 'ramda'
import { expect } from 'chai'
import { Event } from '../../../src/@types/event'
import { eventSchema } from '../../../src/schemas/event-schema'
+import { EventTags } from '../../../src/constants/base'
import { validateSchema } from '../../../src/utils/validation'
describe('NIP-01', () => {
@@ -16,31 +17,31 @@ describe('NIP-01', () => {
'kind': 7,
'tags': [
[
- 'e',
+ EventTags.Event,
'c58e83bb744e4c29642db7a5c3bd1519516ad5c51f6ba5f90c451d03c1961210',
'',
'root',
],
[
- 'e',
+ EventTags.Event,
'd0d78967b734628cec7bdfa2321c71c1f1c48e211b4b54333c3b0e94e7e99166',
'',
'reply',
],
[
- 'p',
+ EventTags.Pubkey,
'edfa27d49d2af37ee331e1225bb6ed1912c6d999281b36d8018ad99bc3573c29',
],
[
- 'p',
+ EventTags.Pubkey,
'32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245',
],
[
- 'e',
+ EventTags.Event,
'6fed2aae1e4f7d8b535774e4f7061c10e2ff20df1ef047da09462c7937925cd5',
],
[
- 'p',
+ EventTags.Pubkey,
'2ef93f01cd2493e04235a6b87b10d3c4a74e2a7eb7c3caf168268f6af73314b5',
],
],
@@ -115,7 +116,6 @@ describe('NIP-01', () => {
],
tag: [
{ message: 'must be an array', transform: assocPath(['tags', 0], null) },
- { message: 'must contain less than or equal to 10 items', transform: assocPath(['tags', 0], range(0, 11).map(() => 'x')) },
],
identifier: [
{ message: 'must be a string', transform: assocPath(['tags', 0, 0], null) },
diff --git a/test/unit/schemas/filter-schema.spec.ts b/test/unit/schemas/filter-schema.spec.ts
index 09f10812..fab8498f 100644
--- a/test/unit/schemas/filter-schema.spec.ts
+++ b/test/unit/schemas/filter-schema.spec.ts
@@ -1,4 +1,4 @@
-import { assocPath, range } from 'ramda'
+import { assocPath } from 'ramda'
import { expect } from 'chai'
import { filterSchema } from '../../../src/schemas/filter-schema'
@@ -32,7 +32,6 @@ describe('NIP-01', () => {
const cases = {
ids: [
{ message: 'must be an array', transform: assocPath(['ids'], null) },
- { message: 'must contain less than or equal to 1000 items', transform: assocPath(['ids'], range(0, 1001).map(() => 'ffff')) },
],
prefixOrId: [
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['ids', 0], 'f'.repeat(65)) },
@@ -41,7 +40,6 @@ describe('NIP-01', () => {
],
authors: [
{ message: 'must be an array', transform: assocPath(['authors'], null) },
- { message: 'must contain less than or equal to 1000 items', transform: assocPath(['authors'], range(0, 1001).map(() => 'ffff')) },
],
prefixOrAuthor: [
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['authors', 0], 'f'.repeat(65)) },
@@ -50,7 +48,6 @@ describe('NIP-01', () => {
],
kinds: [
{ message: 'must be an array', transform: assocPath(['kinds'], null) },
- { message: 'must contain less than or equal to 20 items', transform: assocPath(['kinds'], range(0, 21).map(() => 1)) },
],
kind: [
{ message: 'must be greater than or equal to 0', transform: assocPath(['kinds', 0], -1) },
@@ -73,11 +70,9 @@ describe('NIP-01', () => {
{ message: 'must be a number', transform: assocPath(['limit'], null) },
{ message: 'must be greater than or equal to 0', transform: assocPath(['limit'], -1) },
{ message: 'must be a multiple of 1', transform: assocPath(['limit'], Math.PI) },
- { message: 'must be less than or equal to 5000', transform: assocPath(['limit'], 5001) },
],
'#e': [
{ message: 'must be an array', transform: assocPath(['#e'], null) },
- { message: 'must contain less than or equal to 256 items', transform: assocPath(['#e'], range(0, 1024 + 1).map(() => 'f')) },
],
'#e[0]': [
{ message: 'length must be less than or equal to 1024 characters long', transform: assocPath(['#e', 0], 'f'.repeat(1024 + 1)) },
@@ -85,7 +80,6 @@ describe('NIP-01', () => {
],
'#p': [
{ message: 'must be an array', transform: assocPath(['#p'], null) },
- { message: 'must contain less than or equal to 256 items', transform: assocPath(['#p'], range(0, 1024 + 1).map(() => 'f')) },
],
'#p[0]': [
{ message: 'length must be less than or equal to 1024 characters long', transform: assocPath(['#p', 0], 'f'.repeat(1024 + 1)) },
@@ -93,7 +87,6 @@ describe('NIP-01', () => {
],
'#r': [
{ message: 'must be an array', transform: assocPath(['#r'], null) },
- { message: 'must contain less than or equal to 256 items', transform: assocPath(['#r'], range(0, 1024 + 1).map(() => 'f')) },
],
'#r[0]': [
{ message: 'length must be less than or equal to 1024 characters long', transform: assocPath(['#r', 0], 'f'.repeat(1024 + 1)) },