diff --git a/Dockerfile b/Dockerfile index 833d6062..2ab01f64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,13 +18,6 @@ LABEL org.opencontainers.image.description="nostream" LABEL org.opencontainers.image.authors="Ricardo Arturo Cabral Mejía" LABEL org.opencontainers.image.licenses=MIT - -ENV DB_HOST=localhost -ENV DB_PORT=5432 -ENV DB_NAME=nostr-ts-relay -ENV DB_USER=nostr-ts-relay -ENV DB_PASSWORD=nostr-ts-relay - WORKDIR /app RUN apk add --no-cache --update git diff --git a/docker-compose.yml b/docker-compose.yml index 2dbed725..00986305 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,13 @@ services: - relay: + nostream: build: . - container_name: nostr-ts-relay + container_name: nostream environment: SECRET: changeme RELAY_PORT: 8008 - NOSTR_CONFIG_DIR: /home/node/ # Master - DB_HOST: db + NOSTR_CONFIG_DIR: /home/node/.nostr + DB_HOST: nostream-db DB_PORT: 5432 DB_USER: nostr_ts_relay DB_PASSWORD: nostr_ts_relay @@ -26,7 +26,7 @@ services: RR_DB_MAX_POOL_SIZE: 64 RR_DB_ACQUIRE_CONNECTION_TIMEOUT: 60000 # Redis - REDIS_HOST: cache + REDIS_HOST: nostream-cache REDIS_PORT: 6379 REDIS_USER: default REDIS_PASSWORD: nostr_ts_relay @@ -38,31 +38,31 @@ services: # DEBUG: "primary:*" # DEBUG: "worker:*" # DEBUG: "knex:query" + env_file: + - test.env user: node:node volumes: - - ${PWD}/.nostr:/home/node/ + - ${PWD}/.nostr:/home/node/.nostr ports: - 8008:8008 depends_on: - cache: + nostream-cache: condition: service_healthy - db: + nostream-db: condition: service_healthy - migrations: + nostream-migrate: condition: service_completed_successfully restart: on-failure networks: default: - ipv4_address: 10.10.10.2 - db: + nostream-db: image: postgres - container_name: db + container_name: nostream-db environment: POSTGRES_DB: nostr_ts_relay POSTGRES_USER: nostr_ts_relay POSTGRES_PASSWORD: nostr_ts_relay volumes: - - pgdata:/var/lib/postgresql/data-old - ${PWD}/.nostr/data:/var/lib/postgresql/data - ${PWD}/.nostr/db-logs:/var/log/postgresql - ${PWD}/postgresql.conf:/postgresql.conf @@ -70,7 +70,6 @@ services: - 15432:5432 networks: default: - ipv4_address: 10.10.10.3 command: postgres -c 'config_file=/postgresql.conf' restart: always healthcheck: @@ -79,26 +78,25 @@ services: timeout: 5s retries: 5 start_period: 360s - cache: + nostream-cache: image: redis:7.0.5-alpine3.16 - container_name: cache + container_name: nostream-cache volumes: - cache:/data command: redis-server --loglevel warning --requirepass nostr_ts_relay networks: default: - ipv4_address: 10.10.10.4 restart: always healthcheck: test: [ "CMD", "redis-cli", "ping", "|", "grep", "PONG" ] interval: 1s timeout: 5s retries: 5 - migrations: + nostream-migrate: image: node:18-alpine3.16 - container_name: migrations + container_name: nostream-migrate environment: - DB_HOST: db + DB_HOST: nostream-db DB_PORT: 5432 DB_USER: nostr_ts_relay DB_PASSWORD: nostr_ts_relay @@ -111,14 +109,15 @@ services: - ./migrations:/code/migrations - ./knexfile.js:/code/knexfile.js depends_on: - db: + nostream-db: condition: service_healthy networks: default: ipv4_address: 10.10.10.254 + networks: default: - name: nostr-ts-relay + name: nostream ipam: driver: default config: @@ -126,4 +125,3 @@ networks: volumes: cache: - pgdata: diff --git a/migrations/20230107_230900_create_invoices_table.js b/migrations/20230107_230900_create_invoices_table.js index fa6a731d..93197ff6 100644 --- a/migrations/20230107_230900_create_invoices_table.js +++ b/migrations/20230107_230900_create_invoices_table.js @@ -6,7 +6,7 @@ exports.up = function (knex) { table.bigint('amount_requested').unsigned().notNullable() table.bigint('amount_paid').unsigned() table.enum('unit', ['msats', 'sats', 'btc']) - table.enum('status', ['pending', 'completed']) + table.enum('status', ['pending', 'completed', 'expired']) table.text('description') table.datetime('confirmed_at', { useTz: false, precision: 3 }) table.datetime('expires_at', { useTz: false, precision: 3 }) diff --git a/migrations/20230107_230900_create_users_table.js b/migrations/20230107_230900_create_users_table.js new file mode 100644 index 00000000..fd0d272b --- /dev/null +++ b/migrations/20230107_230900_create_users_table.js @@ -0,0 +1,13 @@ +exports.up = function (knex) { + return knex.schema.createTable('users', (table) => { + table.binary('pubkey').primary() + table.boolean('is_admitted').default(0) + table.bigint('balance').default(0) + table.datetime('tos_accepted_at', { useTz: false, precision: 3 }) + table.timestamps(true, true, false) + }) +} + +exports.down = function (knex) { + return knex.schema.dropTable('users') +} diff --git a/migrations/20230118_190000_confirm_invoice_func.js b/migrations/20230118_190000_confirm_invoice_func.js new file mode 100644 index 00000000..e4948065 --- /dev/null +++ b/migrations/20230118_190000_confirm_invoice_func.js @@ -0,0 +1,70 @@ +// Adapted from: https://github.com/stackernews/stacker.news +// Original Author: Keyan Kousha https://github.com/huumn +/** +MIT License + +Copyright (c) 2023 Keyan Kousha / Stacker News + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +exports.up = async function (knex) { + return knex.schema + .raw(`create function now_utc() returns timestamp as $$ + select now() at time zone 'utc'; +$$ language sql;`) + .raw(`create function ASSERT_SERIALIZED() returns void as $$ +BEGIN + IF (select current_setting('transaction_isolation') <> 'serializable') THEN + RAISE EXCEPTION 'SN_NOT_SERIALIZABLE'; + END IF; +END; +$$ language plpgsql; + `) + .raw( + `CREATE OR REPLACE FUNCTION confirm_invoice(invoice_id UUID, amount_received BIGINT, confirmation_date TIMESTAMP WITHOUT TIME ZONE) +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +DECLARE + payee BYTEA; + confirmed_date TIMESTAMP WITHOUT TIME ZONE; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + SELECT "pubkey", "confirmed_at" INTO payee, confirmed_date FROM "invoices" WHERE id = invoice_id; + IF confirmed_date IS NULL THEN + UPDATE invoices + SET + "confirmed_at" = confirmation_date, + "amount_paid" = amount_received, + "updated_at" = now_utc() + WHERE id = invoice_id; + UPDATE users SET balance = balance + amount_received WHERE "pubkey" = payee; + END IF; + RETURN 0; +END; +$$;`) +} + +exports.down = function (knex) { + return knex.schema + .raw('DROP FUNCTION IF EXISTS confirm_invoice(UUID, BYTEA, TIMESTAMP);') + .raw('DROP FUNCTION IF EXISTS ASSERT_SERIALIZED();') + .raw('DROP FUNCTION IF EXISTS now_utc();') +} diff --git a/migrations/20230119_170900_add_kind_tags_created_at_index.js b/migrations/20230119_170900_add_kind_tags_created_at_index.js new file mode 100644 index 00000000..0eab0dc1 --- /dev/null +++ b/migrations/20230119_170900_add_kind_tags_created_at_index.js @@ -0,0 +1,14 @@ +exports.up = async function (knex) { + return knex.schema + .raw('CREATE EXTENSION btree_gin;') + .raw( + `CREATE INDEX kind_tags_created_at_idx + ON events USING GIN ( event_kind, event_tags, event_created_at );`, + ) +} + +exports.down = function (knex) { + return knex.schema + .raw('DROP INDEX IF EXISTS kind_tags_created_at_idx;') + .raw('DROP EXTENSION btree_gin;') +} diff --git a/migrations/20230120_161800_charge_user_func.js b/migrations/20230120_161800_charge_user_func.js new file mode 100644 index 00000000..b75cecb4 --- /dev/null +++ b/migrations/20230120_161800_charge_user_func.js @@ -0,0 +1,27 @@ +exports.up = async function (knex) { + return knex.schema + .raw( + `CREATE OR REPLACE FUNCTION charge_user(charged_user BYTEA, amount BIGINT) +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +DECLARE + current_balance BIGINT; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + SELECT "balance" INTO current_balance FROM "users" WHERE "pubkey" = charged_user; + IF current_balance - amount >= 0 THEN + UPDATE "users" SET balance = balance - amount WHERE "pubkey" = charged_user; + RETURN 1; + ELSE + RETURN 0; + END IF; +END; +$$;`) +} + +exports.down = function (knex) { + return knex.schema + .raw('DROP FUNCTION IF EXISTS charge_user(BYTEA, BIGINT);') +} diff --git a/migrations/20230126_230000_add_remote_address_to_events_table.js b/migrations/20230126_230000_add_remote_address_to_events_table.js new file mode 100644 index 00000000..48fbcfd2 --- /dev/null +++ b/migrations/20230126_230000_add_remote_address_to_events_table.js @@ -0,0 +1,9 @@ +exports.up = function (knex) { + return knex.raw('ALTER TABLE events ADD remote_address inet NULL;') +} + +exports.down = function (knex) { + return knex.schema.alterTable('events', function (table) { + table.dropColumn('remote_address') + }) +} diff --git a/package-lock.json b/package-lock.json index a4510a43..5472e231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "bech32": "2.0.0", "body-parser": "1.20.1", "debug": "4.3.4", + "dinero.js": "2.0.0-alpha.13", "dotenv": "16.0.3", "express": "4.18.2", "helmet": "6.0.1", @@ -44,7 +45,7 @@ "@types/express": "4.17.15", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.24", + "@types/node": "18.11.18", "@types/pg": "^8.6.5", "@types/ramda": "^0.28.13", "@types/sinon": "^10.0.11", @@ -1072,6 +1073,27 @@ "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", "dev": true }, + "node_modules/@dinero.js/calculator-number": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/calculator-number/-/calculator-number-2.0.0-alpha.13.tgz", + "integrity": "sha512-Mo/0FUVFq4dVb/RfBIASUxPFbQbkHYC0VBe36DgWfbbhwG6K6ONuzTLBU5W6LdrMpB7D/CxgZISdQ0pyZKwg9w==", + "dependencies": { + "@dinero.js/core": "2.0.0-alpha.13" + } + }, + "node_modules/@dinero.js/core": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/core/-/core-2.0.0-alpha.13.tgz", + "integrity": "sha512-RLZz8WmYe1ehqD2YtTpzCzdk0Hqf6oaAVmRlULLkua6vuEVEXjPQEhuyTMLHcS6/Gppy9nuEw8mpSNvU+Sy8QQ==", + "dependencies": { + "@dinero.js/currencies": "2.0.0-alpha.13" + } + }, + "node_modules/@dinero.js/currencies": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/currencies/-/currencies-2.0.0-alpha.13.tgz", + "integrity": "sha512-vEfDara+xAqOrLww80FyFVw4usWcCsR4HwhaoCvwIzB5UdsLPDeUviAGFi8CoWjtLCR44iIeOHdn9jDr7Jj+TQ==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -1893,9 +1915,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", - "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -3968,6 +3990,16 @@ "node": ">=0.3.1" } }, + "node_modules/dinero.js": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/dinero.js/-/dinero.js-2.0.0-alpha.13.tgz", + "integrity": "sha512-xuBgE/3/67SXOW/UKjmaRd/YJQkUSlYS+6DvfglFg1Mbfn2K7cp+WQcgU76CLJpJPkJABgILsPjw1Om9OJ5puA==", + "dependencies": { + "@dinero.js/calculator-number": "2.0.0-alpha.13", + "@dinero.js/core": "2.0.0-alpha.13", + "@dinero.js/currencies": "2.0.0-alpha.13" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -14003,6 +14035,27 @@ "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", "dev": true }, + "@dinero.js/calculator-number": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/calculator-number/-/calculator-number-2.0.0-alpha.13.tgz", + "integrity": "sha512-Mo/0FUVFq4dVb/RfBIASUxPFbQbkHYC0VBe36DgWfbbhwG6K6ONuzTLBU5W6LdrMpB7D/CxgZISdQ0pyZKwg9w==", + "requires": { + "@dinero.js/core": "2.0.0-alpha.13" + } + }, + "@dinero.js/core": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/core/-/core-2.0.0-alpha.13.tgz", + "integrity": "sha512-RLZz8WmYe1ehqD2YtTpzCzdk0Hqf6oaAVmRlULLkua6vuEVEXjPQEhuyTMLHcS6/Gppy9nuEw8mpSNvU+Sy8QQ==", + "requires": { + "@dinero.js/currencies": "2.0.0-alpha.13" + } + }, + "@dinero.js/currencies": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@dinero.js/currencies/-/currencies-2.0.0-alpha.13.tgz", + "integrity": "sha512-vEfDara+xAqOrLww80FyFVw4usWcCsR4HwhaoCvwIzB5UdsLPDeUviAGFi8CoWjtLCR44iIeOHdn9jDr7Jj+TQ==" + }, "@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -14679,9 +14732,9 @@ "dev": true }, "@types/node": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", - "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "@types/normalize-package-data": { @@ -16238,6 +16291,16 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "dinero.js": { + "version": "2.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/dinero.js/-/dinero.js-2.0.0-alpha.13.tgz", + "integrity": "sha512-xuBgE/3/67SXOW/UKjmaRd/YJQkUSlYS+6DvfglFg1Mbfn2K7cp+WQcgU76CLJpJPkJABgILsPjw1Om9OJ5puA==", + "requires": { + "@dinero.js/calculator-number": "2.0.0-alpha.13", + "@dinero.js/core": "2.0.0-alpha.13", + "@dinero.js/currencies": "2.0.0-alpha.13" + } + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", diff --git a/package.json b/package.json index 08501d18..e9a61a29 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@types/express": "4.17.15", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.24", + "@types/node": "18.11.18", "@types/pg": "^8.6.5", "@types/ramda": "^0.28.13", "@types/sinon": "^10.0.11", @@ -116,6 +116,7 @@ "bech32": "2.0.0", "body-parser": "1.20.1", "debug": "4.3.4", + "dinero.js": "2.0.0-alpha.13", "dotenv": "16.0.3", "express": "4.18.2", "helmet": "6.0.1", diff --git a/resources/default-settings.yaml b/resources/default-settings.yaml index 2e7e204e..22222e93 100755 --- a/resources/default-settings.yaml +++ b/resources/default-settings.yaml @@ -22,10 +22,12 @@ payments: whitelists: pubkeys: - replace-with-your-pubkey -paymentProcessors: +paymentsProcessors: zebedee: baseURL: https://api.zebedee.io/ callbackBaseURL: https://nostream.your-domain.com/callbacks/zebedee + ipWhitelist: + - "::ffff:3.225.112.64" network: maxPayloadSize: 131072 remoteIpHeader: x-forwarded-for @@ -36,22 +38,24 @@ limits: invoice: rateLimits: - period: 60000 - rate: 1 + rate: 3 - period: 3600000 - rate: 30 + rate: 10 - period: 86400000 - rate: 360 + rate: 20 ipWhitelist: - "::1" - "::ffff:10.10.10.1" connection: rateLimits: + - period: 1000 + rate: 6 - period: 60000 - rate: 12 + rate: 30 - period: 3600000 - rate: 360 + rate: 300 - period: 86400000 - rate: 2880 + rate: 1440 ipWhitelist: - "::1" - "::ffff:10.10.10.1" @@ -62,7 +66,7 @@ limits: whitelist: [] blacklist: [] pubkey: - minBalanceMsats: 0 + minBalance: 0 minLeadingZeroBits: 0 whitelist: [] blacklist: [] diff --git a/resources/index.html b/resources/index.html index a8bb35de..a05bf754 100644 --- a/resources/index.html +++ b/resources/index.html @@ -3,12 +3,9 @@ - Pay To Relay - {{name}} + Admission Fee Required - {{name}} -
@@ -33,7 +30,7 @@

{{name}}

- +
Hex or npub formats accepted.
@@ -49,6 +46,7 @@

{{name}}

+
@@ -116,9 +114,9 @@
diff --git a/resources/invoices.html b/resources/invoices.html index c33cd2f5..fe5cd9db 100644 --- a/resources/invoices.html +++ b/resources/invoices.html @@ -3,7 +3,7 @@ - Pay Invoice - {{name}} + Invoice Payment - {{name}}