better error handling

This commit is contained in:
Pablo Fernandez 2024-01-02 11:11:06 +00:00
commit 67c5252983
16 changed files with 931 additions and 125 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "nsecbunkerd", "name": "nsecbunkerd",
"version": "0.9.0", "version": "0.10.0",
"description": "nsecbunker daemon", "description": "nsecbunker daemon",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
@ -43,6 +43,8 @@
"@prisma/client": "^5.4.1", "@prisma/client": "^5.4.1",
"@scure/base": "^1.1.1", "@scure/base": "^1.1.1",
"@types/yargs": "^17.0.24", "@types/yargs": "^17.0.24",
"axios": "^1.6.2",
"bcrypt": "^5.1.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
@ -54,6 +56,8 @@
"gravatar": "^1.8.2", "gravatar": "^1.8.2",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"lnbits": "^1.1.5",
"lnbits-ts": "^0.0.2",
"nostr-tools": "^1.17.0", "nostr-tools": "^1.17.0",
"websocket-polyfill": "^0.0.3", "websocket-polyfill": "^0.0.3",
"ws": "^8.13.0", "ws": "^8.13.0",

322
pnpm-lock.yaml generated
View file

@ -29,6 +29,12 @@ dependencies:
'@types/yargs': '@types/yargs':
specifier: ^17.0.24 specifier: ^17.0.24
version: 17.0.24 version: 17.0.24
axios:
specifier: ^1.6.2
version: 1.6.2(debug@4.3.4)
bcrypt:
specifier: ^5.1.1
version: 5.1.1
crypto-js: crypto-js:
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
@ -62,6 +68,12 @@ dependencies:
isomorphic-ws: isomorphic-ws:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.0(ws@8.13.0) version: 5.0.0(ws@8.13.0)
lnbits:
specifier: ^1.1.5
version: 1.1.5(debug@4.3.4)
lnbits-ts:
specifier: ^0.0.2
version: 0.0.2(debug@4.3.4)
nostr-tools: nostr-tools:
specifier: ^1.17.0 specifier: ^1.17.0
version: 1.17.0(typescript@5.1.3) version: 1.17.0(typescript@5.1.3)
@ -562,6 +574,24 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
dev: true dev: true
/@mapbox/node-pre-gyp@1.0.11:
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
dependencies:
detect-libc: 2.0.2
https-proxy-agent: 5.0.1
make-dir: 3.1.0
node-fetch: 2.7.0
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.5.4
tar: 6.2.0
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/@noble/ciphers@0.2.0: /@noble/ciphers@0.2.0:
resolution: {integrity: sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==} resolution: {integrity: sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==}
dev: false dev: false
@ -717,6 +747,10 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: false dev: false
/abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: false
/abort-controller@3.0.0: /abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'} engines: {node: '>=6.5'}
@ -761,6 +795,15 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/ajv-formats@2.1.1(ajv@8.12.0): /ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies: peerDependencies:
@ -821,10 +864,22 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: true dev: true
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
dev: false
/archy@1.0.0: /archy@1.0.0:
resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
dev: false dev: false
/are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
dev: false
/arg@4.1.3: /arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true dev: true
@ -880,6 +935,10 @@ packages:
es-shim-unscopables: 1.0.0 es-shim-unscopables: 1.0.0
dev: false dev: false
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/atomic-sleep@1.0.0: /atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@ -900,6 +959,24 @@ packages:
- supports-color - supports-color
dev: false dev: false
/axios@0.21.4(debug@4.3.4):
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.3(debug@4.3.4)
transitivePeerDependencies:
- debug
dev: false
/axios@1.6.2(debug@4.3.4):
resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==}
dependencies:
follow-redirects: 1.15.3(debug@4.3.4)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/balanced-match@1.0.2: /balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -907,6 +984,18 @@ packages:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false dev: false
/bcrypt@5.1.1:
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
engines: {node: '>= 10.0.0'}
requiresBuild: true
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
node-addon-api: 5.1.0
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/binary-extensions@2.2.0: /binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1028,6 +1117,11 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
dev: false
/cli-spinners@2.9.0: /cli-spinners@2.9.0:
resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1066,6 +1160,18 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false dev: false
/color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/commander@4.1.1: /commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -1074,6 +1180,10 @@ packages:
/concat-map@0.0.1: /concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: false
/content-disposition@0.5.4: /content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -1173,6 +1283,15 @@ packages:
object-keys: 1.1.1 object-keys: 1.1.1
dev: false dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false
/depd@2.0.0: /depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -1183,6 +1302,11 @@ packages:
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dev: false dev: false
/detect-libc@2.0.2:
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
engines: {node: '>=8'}
dev: false
/diff@4.0.2: /diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
@ -1802,12 +1926,33 @@ packages:
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
dev: false dev: false
/follow-redirects@1.15.3(debug@4.3.4):
resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 4.3.4
dev: false
/for-each@0.3.3: /for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
dev: false dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/formdata-polyfill@4.0.10: /formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'} engines: {node: '>=12.20.0'}
@ -1825,6 +1970,13 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
dev: false
/fs.realpath@1.0.0: /fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@ -1854,6 +2006,21 @@ packages:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: false dev: false
/gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
dependencies:
aproba: 2.0.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
dev: false
/get-caller-file@2.0.5: /get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
@ -2009,6 +2176,10 @@ packages:
has-symbols: 1.0.3 has-symbols: 1.0.3
dev: false dev: false
/has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
dev: false
/has@1.0.3: /has@1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@ -2031,6 +2202,16 @@ packages:
toidentifier: 1.0.1 toidentifier: 1.0.1
dev: false dev: false
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/human-signals@2.1.0: /human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
@ -2320,6 +2501,23 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true dev: true
/lnbits-ts@0.0.2(debug@4.3.4):
resolution: {integrity: sha512-3OnkL/IILpPQ0SUkN1kBdAO1dBmV+QqQNIh1GSz3koBqwjnFM8+RoZDKMsiq8WWHRaVFH49OA+a2ODgrC9sH/Q==}
dependencies:
axios: 1.6.2(debug@4.3.4)
transitivePeerDependencies:
- debug
dev: false
/lnbits@1.1.5(debug@4.3.4):
resolution: {integrity: sha512-RPCBNsKKxlyQTHPKdU66iiXFBz6SuISVVkxJoSZY3Z+CBEzOu6xpgzZtQcZTbc1BCLqQc6HeK4qtfByKWjBTmg==}
dependencies:
axios: 0.21.4(debug@4.3.4)
typescript: 4.9.5
transitivePeerDependencies:
- debug
dev: false
/load-tsconfig@0.2.5: /load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -2354,6 +2552,13 @@ packages:
yallist: 4.0.0 yallist: 4.0.0
dev: false dev: false
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
dependencies:
semver: 6.3.0
dev: false
/make-error@1.3.6: /make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true dev: true
@ -2421,6 +2626,32 @@ packages:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: false dev: false
/minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
dev: false
/minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
dev: false
/minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
yallist: 4.0.0
dev: false
/mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: false
/ms@2.0.0: /ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false dev: false
@ -2462,11 +2693,27 @@ packages:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false dev: false
/node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
dev: false
/node-domexception@1.0.0: /node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
dev: false dev: false
/node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-fetch@3.3.2: /node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -2481,6 +2728,14 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
dependencies:
abbrev: 1.1.1
dev: false
/normalize-path@3.0.0: /normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2510,10 +2765,18 @@ packages:
path-key: 3.1.1 path-key: 3.1.1
dev: true dev: true
/npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
dev: false
/object-assign@4.1.1: /object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/object-inspect@1.12.3: /object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
@ -2740,6 +3003,10 @@ packages:
ipaddr.js: 1.9.1 ipaddr.js: 1.9.1
dev: false dev: false
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/punycode@2.3.0: /punycode@2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2785,6 +3052,15 @@ packages:
unpipe: 1.0.0 unpipe: 1.0.0
dev: false dev: false
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readable-stream@4.4.2: /readable-stream@4.4.2:
resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==} resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -2997,7 +3273,6 @@ packages:
/signal-exit@3.0.7: /signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: true
/slash@3.0.0: /slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
@ -3120,6 +3395,18 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: false dev: false
/tar@6.2.0:
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
engines: {node: '>=10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: false
/text-table@0.2.0: /text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: false dev: false
@ -3167,6 +3454,10 @@ packages:
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
dev: false dev: false
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/tr46@1.0.1: /tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
dependencies: dependencies:
@ -3317,6 +3608,12 @@ packages:
resolution: {integrity: sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==} resolution: {integrity: sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==}
dev: false dev: false
/typescript@4.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
/typescript@5.1.3: /typescript@5.1.3:
resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@ -3363,6 +3660,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: false dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
/utils-merge@1.0.1: /utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@ -3382,6 +3683,10 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: false dev: false
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/webidl-conversions@4.0.2: /webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true dev: true
@ -3409,6 +3714,13 @@ packages:
- supports-color - supports-color
dev: false dev: false
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/whatwg-url@7.1.0: /whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
dependencies: dependencies:
@ -3450,6 +3762,12 @@ packages:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 4.2.3
dev: false
/wordwrap@1.0.0: /wordwrap@1.0.0:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
dev: false dev: false

View file

@ -1,13 +1,25 @@
import { readFileSync, writeFileSync } from 'fs'; import { readFileSync, writeFileSync } from 'fs';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import { NDKPrivateKeySigner, NDKUserProfile } from '@nostr-dev-kit/ndk';
import { IAdminOpts } from '../daemon/admin'; import { IAdminOpts } from '../daemon/admin';
import { version } from '../../package.json'; import { version } from '../../package.json';
const generatedKey = NDKPrivateKeySigner.generate(); const generatedKey = NDKPrivateKeySigner.generate();
export type LNBitsWalletConfig = {
url: string,
key: string,
nostdressUrl: string,
}
export interface IWalletConfig {
lnbits?: LNBitsWalletConfig;
}
export interface DomainConfig { export interface DomainConfig {
nip05: string; nip05: string;
wallet?: IWalletConfig;
defaultProfile?: Record<string, string>;
}; };
export interface IConfig { export interface IConfig {

View file

@ -0,0 +1,89 @@
import axios from "axios";
import createDebug from "debug";
import { IWalletConfig, LNBitsWalletConfig } from "../../../../config";
const debug = createDebug("nsecbunker:wallet");
export async function generateWallet(
walletConfig: IWalletConfig,
username: string,
domain: string,
npub: string
) {
debug("generateWallet", walletConfig, username, domain, npub);
if (walletConfig.lnbits) {
return generateLNBitsWallet(walletConfig.lnbits, username, domain, npub);
}
}
export async function generateLNBitsWallet(
lnbitsConfig: LNBitsWalletConfig,
username: string,
domain: string,
npub: string
) {
debug("generateLNBitsWallet", lnbitsConfig, username, domain, npub);
const url = new URL(lnbitsConfig.url);
url.pathname = '/usermanager/api/v1/users';
const res = await axios.post(url.toString(), {
user_name: username,
wallet_name: `${username}@${domain}`,
}, {
headers: {
"X-Api-Key": lnbitsConfig.key,
},
});
const user = res.data;
const wallet = user.wallets[0];
debug("lnbits response: ", {status: res.status, data: res.data});
return await generateLNAddress(
username,
domain,
wallet.inkey,
npub,
'lnbits',
lnbitsConfig.url,
lnbitsConfig.nostdressUrl,
);
}
export async function generateLNAddress(
username: string,
domain: string,
userInvoiceKey: string,
userNpub: string,
kind: string,
host: string,
nostdressUrl: string
) {
debug("generateLNAddress", username, domain, userInvoiceKey, userNpub, kind, host, nostdressUrl);
const formData = new URLSearchParams();
formData.append('name', username);
formData.append('domain', domain);
formData.append('kind', kind);
formData.append('host', host);
formData.append('key', userInvoiceKey);
formData.append('pin', ' ');
formData.append('npub', userNpub);
formData.append('currentName', ' ');
const url = new URL(nostdressUrl);
url.pathname = '/api/easy/';
debug("nostdress urL: ", url.toString());
const res = await axios.post(url.toString(), formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
debug("nostdress response: ", res.data);
return `${username}@${domain}`;
}

View file

@ -6,7 +6,11 @@ import { IConfig, getCurrentConfig, saveCurrentConfig } from "../../../config";
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
import { allowAllRequestsFromKey } from "../../lib/acl"; import { allowAllRequestsFromKey } from "../../lib/acl";
import { requestAuthorization } from "../../authorize"; import { requestAuthorization } from "../../authorize";
import { generateWallet } from "./account/wallet";
import prisma from "../../../db"; import prisma from "../../../db";
import createDebug from "debug";
const debug = createDebug("nsecbunker:createAccount");
export async function validate(currentConfig, username: string, domain: string, email?: string) { export async function validate(currentConfig, username: string, domain: string, email?: string) {
if (!username) { if (!username) {
@ -26,10 +30,19 @@ export async function validate(currentConfig, username: string, domain: string,
} }
} }
const emptyNip05File = {
names: {},
relays: {},
}
async function getCurrentNip05File(currentConfig: any, domain: string) { async function getCurrentNip05File(currentConfig: any, domain: string) {
const nip05File = currentConfig.domains[domain].nip05; try {
const file = readFileSync(nip05File, 'utf8'); const nip05File = currentConfig.domains[domain].nip05;
return JSON.parse(file); const file = readFileSync(nip05File, 'utf8');
return JSON.parse(file);
} catch (e: any) {
return emptyNip05File;
}
} }
/** /**
@ -47,12 +60,26 @@ async function addNip05(currentConfig: IConfig, username: string, domain: string
writeFileSync(nip05File, JSON.stringify(currentNip05s, null, 2)); writeFileSync(nip05File, JSON.stringify(currentNip05s, null, 2));
} }
/**
* Reserved usernames that cannot be used since someone might
* confuse them with some type of authority of this domain
* and scammers are scoundrels
*/
const RESERVED_USERNAMES = [
"admin", "root", "_", "administrator", "__"
];
async function validateUsername(username: string | undefined, domain: string, admin: AdminInterface, req: NDKRpcRequest) { async function validateUsername(username: string | undefined, domain: string, admin: AdminInterface, req: NDKRpcRequest) {
if (!username || username.length === 0) { if (!username || username.length === 0) {
// create a random username of 10 characters // create a random username of 10 characters
username = Math.random().toString(36).substring(2, 15); username = Math.random().toString(36).substring(2, 15);
} }
// check if the username is available
if (RESERVED_USERNAMES.includes(username)) {
throw new Error('username not available');
}
return username; return username;
} }
@ -87,8 +114,7 @@ export default async function createAccount(admin: AdminInterface, req: NDKRpcRe
const payload: string[] = [ username, domain ]; const payload: string[] = [ username, domain ];
if (email) payload.push(email); if (email) payload.push(email);
console.log('requesting authorization', payload); debug(`Requesting authorization for ${nip05}`);
const authorizationWithPayload = await requestAuthorization( const authorizationWithPayload = await requestAuthorization(
admin, admin,
nip05, nip05,
@ -97,7 +123,7 @@ export default async function createAccount(admin: AdminInterface, req: NDKRpcRe
req.method, req.method,
JSON.stringify(payload) JSON.stringify(payload)
); );
console.log('authorizationWithPayload', authorizationWithPayload); debug(`Authorization for ${nip05} ${authorizationWithPayload ? 'granted' : 'denied'}`);
if (authorizationWithPayload) { if (authorizationWithPayload) {
const payload = JSON.parse(authorizationWithPayload); const payload = JSON.parse(authorizationWithPayload);
@ -108,6 +134,9 @@ export default async function createAccount(admin: AdminInterface, req: NDKRpcRe
} }
} }
/**
* This is where the real work of creating the private key, wallet, nip-05, granting access, etc happen
*/
export async function createAccountReal( export async function createAccountReal(
admin: AdminInterface, admin: AdminInterface,
req: NDKRpcRequest, req: NDKRpcRequest,
@ -115,13 +144,15 @@ export async function createAccountReal(
domain: string, domain: string,
email?: string email?: string
) { ) {
// Fetch record since the authorization backend might have changed it
console.log('creating account');
try { try {
const currentConfig = await getCurrentConfig(admin.configFile); const currentConfig = await getCurrentConfig(admin.configFile);
if (!currentConfig.domains) {
throw new Error('no domains configured');
}
const domainConfig = currentConfig.domains[domain];
await validate(currentConfig, username, domain, email); await validate(currentConfig, username, domain, email);
const nip05 = `${username}@${domain}`; const nip05 = `${username}@${domain}`;
@ -129,14 +160,39 @@ export async function createAccountReal(
const profile: NDKUserProfile = { const profile: NDKUserProfile = {
display_name: username, display_name: username,
name: username, name: username,
nip05 nip05,
...(domainConfig.defaultProfile || {})
}; };
setupSkeletonProfile(key, profile, email);
const generatedUser = await key.user(); const generatedUser = await key.user();
debug(`Created user ${generatedUser.npub} for ${nip05}`);
// Add NIP-05
await addNip05(currentConfig, username, domain, generatedUser.pubkey); await addNip05(currentConfig, username, domain, generatedUser.pubkey);
debug(`Added NIP-05 for ${nip05}`);
// Create wallet
if (domainConfig.wallet) {
generateWallet(
domainConfig.wallet,
username, domain, generatedUser.npub
).then((lnaddress) => {
debug(`wallet for ${nip05}`, {lnaddress});
if (lnaddress) profile.lud16 = lnaddress;
}).catch((e) => {
debug(`error generating wallet for ${nip05}`, e);
}).finally(() => {
debug(`saving profile for ${nip05}`, profile);
setupSkeletonProfile(key, profile, email);
})
} else {
debug(`no wallet configuration for ${domain}`);
// Create user profile
setupSkeletonProfile(key, profile, email);
}
const keyName = nip05; const keyName = nip05;
const nsec = nip19.nsecEncode(key.privateKey!); const nsec = nip19.nsecEncode(key.privateKey!);
currentConfig.keys[keyName] = { key: key.privateKey }; currentConfig.keys[keyName] = { key: key.privateKey };
@ -148,6 +204,8 @@ export async function createAccountReal(
await prisma.key.create({ data: { keyName, pubkey: generatedUser.pubkey } }); await prisma.key.create({ data: { keyName, pubkey: generatedUser.pubkey } });
// Immediately grant access to the creator key // Immediately grant access to the creator key
// This means that the client creating this account can immediately
// access it without having to go through an approval flow
await grantPermissions(req, keyName); await grantPermissions(req, keyName);
return admin.rpc.sendResponse(req.id, req.pubkey, generatedUser.pubkey, NDKKind.NostrConnectAdmin); return admin.rpc.sendResponse(req.id, req.pubkey, generatedUser.pubkey, NDKKind.NostrConnectAdmin);

View file

@ -1,7 +1,7 @@
import "websocket-polyfill"; import "websocket-polyfill";
import NDK, { NDKEvent, NDKKind, NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; import NDK, { NDKKind, NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser } from '@nostr-dev-kit/ndk';
import { NDKNostrRpc } from '@nostr-dev-kit/ndk'; import { NDKNostrRpc } from '@nostr-dev-kit/ndk';
import { debug } from 'debug'; import createDebug from 'debug';
import { Key, KeyUser } from '../run'; import { Key, KeyUser } from '../run';
import { allowAllRequestsFromKey } from '../lib/acl/index.js'; import { allowAllRequestsFromKey } from '../lib/acl/index.js';
import prisma from '../../db'; import prisma from '../../db';
@ -18,6 +18,8 @@ import { validateRequestFromAdmin } from './validations/request-from-admin';
import { dmUser } from '../../utils/dm-user'; import { dmUser } from '../../utils/dm-user';
import { IConfig, getCurrentConfig } from "../../config"; import { IConfig, getCurrentConfig } from "../../config";
const debug = createDebug("nsecbunker:admin");
export type IAdminOpts = { export type IAdminOpts = {
npubs: string[]; npubs: string[];
adminRelays: string[]; adminRelays: string[];
@ -74,7 +76,7 @@ class AdminInterface {
}); });
}); });
this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug("ndk:rpc")); this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug);
} }
public async config(): Promise<IConfig> { public async config(): Promise<IConfig> {
@ -149,7 +151,7 @@ class AdminInterface {
); );
} }
} catch (err: any) { } catch (err: any) {
console.error(`Error handling request ${req.method}: ${err?.message??err}`, req.params); debug(`Error handling request ${req.method}: ${err?.message??err}`, req.params);
return this.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, err?.message); return this.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, err?.message);
} }
} }

View file

@ -32,8 +32,9 @@ export async function requestAuthorization(
if (baseUrl) { if (baseUrl) {
// If we have a URL, request authorization through web // If we have a URL, request authorization through web
urlAuthFlow(baseUrl, admin, remotePubkey, requestId, request, resolve, reject); urlAuthFlow(baseUrl, admin, remotePubkey, requestId, request, resolve, reject);
} else {
adminAuthFlow(admin, keyName, remotePubkey, method, param, resolve, reject);
} }
adminAuthFlow(admin, keyName, remotePubkey, method, param, resolve, reject);
}); });
} }
@ -41,8 +42,10 @@ async function adminAuthFlow(adminInterface, keyName, remotePubkey, method, para
const requestedPerm = await adminInterface.requestPermission(keyName, remotePubkey, method, param); const requestedPerm = await adminInterface.requestPermission(keyName, remotePubkey, method, param);
if (requestedPerm) { if (requestedPerm) {
console.log('resolve adminAuthFlow', !!requestedPerm);
resolve(); resolve();
} else { } else {
console.log('reject adminAuthFlow', !!requestedPerm);
reject(); reject();
} }
} }
@ -54,8 +57,6 @@ async function createRecord(
method: string, method: string,
param?: string | NDKEvent, param?: string | NDKEvent,
) { ) {
console.trace('createRecord', { keyName, requestId, remotePubkey, method, param});
let params: string | undefined; let params: string | undefined;
if (param?.rawEvent) { if (param?.rawEvent) {
@ -114,6 +115,7 @@ export function urlAuthFlow(
if (record.allowed === false) { if (record.allowed === false) {
reject(record.payload); reject(record.payload);
} }
console.log('resolve urlAuthFlow', !!record.params);
resolve(record.params); resolve(record.params);
} }
}, 100); }, 100);

View file

@ -1,17 +1,23 @@
import NDK, { NDKEvent, NDKPrivateKeySigner, NostrEvent, type NDKUserProfile } from "@nostr-dev-kit/ndk"; import NDK, { NDKEvent, NDKPrivateKeySigner, NostrEvent, type NDKUserProfile } from "@nostr-dev-kit/ndk";
import * as CryptoJS from 'crypto-js'; import * as CryptoJS from 'crypto-js';
import createDebug from "debug";
const debug = createDebug("nsecbunker:profile");
const explicitRelayUrls = [ const explicitRelayUrls = [
'wss://purplepag.es', 'wss://purplepag.es',
'wss://relay.damus.io', 'wss://relay.damus.io',
'wss://relay.nostr.band', 'wss://relay.nostr.band',
'wss://nos.lol', 'wss://nos.lol',
"wss://nostr.mutinywallet.com"
]; ];
/** /**
* Setup a skeleton profile for a new key since * Setup a skeleton profile for a new key since
* the experience of a completely empty profile * the experience of a completely empty profile
* is pretty bad when logging in with Coracle * is pretty bad when logging in with Coracle.
*
* @param email - if provided, will fetch the gravatar
*/ */
export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: NDKUserProfile, email?: string) { export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: NDKUserProfile, email?: string) {
const rand = Math.random().toString(36).substring(7); const rand = Math.random().toString(36).substring(7);
@ -27,9 +33,9 @@ export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: N
const hash = CryptoJS.MD5(trimmedEmail); const hash = CryptoJS.MD5(trimmedEmail);
const shash = hash.toString(CryptoJS.enc.Hex); const shash = hash.toString(CryptoJS.enc.Hex);
profile.image = `https://robohash.org/${shash}?gravatar=hashed&set=set5`; profile.image = `https://robohash.org/${shash}?gravatar=hashed&set=set5`;
console.log('fetching gravatar', profile.image); debug('fetching gravatar', profile.image);
} catch (e) { } catch (e) {
console.log('error fetching gravatar', e); debug('error fetching gravatar', e);
} }
} }
@ -50,17 +56,18 @@ export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: N
await event.sign(key); await event.sign(key);
const t = await event.publish(); const t = await event.publish();
console.log(t); debug(`Published to ${t.size} relays`);
event = new NDKEvent(ndk, { event = new NDKEvent(ndk, {
kind: 3, kind: 3,
tags: [ tags: [
['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'], ['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'],
['p', user.pubkey],
], ],
pubkey: user.pubkey, pubkey: user.pubkey,
} as NostrEvent); } as NostrEvent);
await event.sign(key); await event.sign(key);
console.log(`trying to publish profile`, event.rawEvent()); debug(`follow list event`, event.rawEvent());
await event.publish(); await event.publish();
const relays = new NDKEvent(ndk, { const relays = new NDKEvent(ndk, {
@ -69,7 +76,9 @@ export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: N
['r', 'wss://purplepag.es'], ['r', 'wss://purplepag.es'],
['r', 'wss://relay.f7z.io'], ['r', 'wss://relay.f7z.io'],
['r', 'wss://relay.damus.io'], ['r', 'wss://relay.damus.io'],
['r', 'wss://nos.lol'], ['r', 'wss://relayable.org'],
['r', 'wss://relay.nostr.band'],
['r', 'wss://relay.primal.net'],
], ],
pubkey: user.pubkey, pubkey: user.pubkey,
} as NostrEvent); } as NostrEvent);

View file

@ -190,6 +190,7 @@ class Daemon {
async startKeys() { async startKeys() {
console.log('🔑 Starting keys', Object.keys(this.config.keys)); console.log('🔑 Starting keys', Object.keys(this.config.keys));
for (const [name, nsec] of Object.entries(this.config.keys)) { for (const [name, nsec] of Object.entries(this.config.keys)) {
console.log(`🔑 Starting ${name}...`);
await this.startKey(name, nsec); await this.startKey(name, nsec);
} }

View file

@ -1,32 +1,107 @@
import prisma from "../../db"; import prisma from "../../db";
import type { Request } from "@prisma/client"; import bcrypt from "bcrypt";
import { IAllowScope, allowAllRequestsFromKey } from "../lib/acl"; import { IAllowScope, allowAllRequestsFromKey } from "../lib/acl";
import createDebug from "debug";
import { validateRegistration } from "./registration-validations";
export async function authorizeRequestWebHandler(request, reply) { const debug = createDebug("nsecbunker:authorize");
/**
* TODO: This is still nto being used as no JWT is ever created
*/
async function validateAuthCookie(request) {
const cookies = request.cookies || {};
const jwt = cookies.jwt;
if (!jwt) {
return false;
}
const user = await prisma.user.findUnique({
where: { pubkey: jwt }
});
if (!user) {
return false;
}
return true;
}
async function getAndValidateStateOfRequest(request) {
const record = await prisma.request.findUnique({ const record = await prisma.request.findUnique({
where: { id: request.params.id } where: { id: request.params.id }
}); });
const reqCookies = request.cookies;
const url = new URL(request.url, `http://${request.headers.host}`); if (!record || record.allowed !== null) {
const callbackUrl = url.searchParams.get("callbackUrl"); throw new Error("Request not found or already processed");
}
const method = record.method;
let email: string | undefined; return record;
let username: string | undefined; }
let domain: string | undefined;
let nip05: string | undefined;
/**
if (method === "create_account") { * Generates the view to authorize a request
const payload = JSON.parse(record.params); */
const [ username, domain, email ] = payload; export async function authorizeRequestWebHandler(request, reply) {
nip05 = `${username}@${domain}`; try {
const record = await getAndValidateStateOfRequest(request);
return reply.view("/templates/createAccount.handlebar", { record, email, username, domain, nip05, callbackUrl }); const url = new URL(request.url, `http://${request.headers.host}`);
} else { const callbackUrl = url.searchParams.get("callbackUrl");
return reply.view("/templates/authorizeRequest.handlebar", { record, email, username, domain, nip05, callbackUrl });
const method = record.method;
let nip05: string | undefined;
if (method === "create_account") {
const [ username, domain, email ] = JSON.parse(record.params!);
nip05 = `${username}@${domain}`;
return reply.view("/templates/createAccount.handlebar", { record, email, username, domain, nip05, callbackUrl });
} else {
const authorized = validateAuthCookie(request);
return reply.view("/templates/authorizeRequest.handlebar", { record, callbackUrl, authorized });
}
} catch (error: any) {
debug(`Error processing request`, error, request);
return reply.view("/templates/error.handlebar", { error: error.message });
}
}
/**
* Validates + authenticates a request POSTed to the authorize endpoint
*/
export async function validateRequest(request, record) {
if (await validateAuthCookie(request)) {
debug("Already authenticated");
return true;
}
const keyName = record.keyName;
const [username, domain] = keyName.split("@");
if (!username || !domain) {
throw new Error("Invalid keyName");
}
const password = request.body.password;
const userRecord = await prisma.user.findUnique({
where: { username, domain }
});
if (!userRecord) {
debug("No user record found");
throw new Error("No user record found");
}
const hashedPassword = userRecord.password;
const match = await bcrypt.compare(password, hashedPassword);
if (!match) {
debug("Provided password didn't match")
throw new Error("Invalid password");
} }
// return record;
} }
export async function processRequestWebHandler(request, reply) { export async function processRequestWebHandler(request, reply) {
@ -34,26 +109,25 @@ export async function processRequestWebHandler(request, reply) {
where: { id: request.params.id } where: { id: request.params.id }
}); });
if (!record) { if (!record || !record.keyName) {
return; return;
} }
try {
await validateRequest(request, record);
} catch (e: any) {
reply.status(401);
reply.type("application/json");
return reply.send({ ok: false, error: e.message });
}
await prisma.request.update({ await prisma.request.update({
where: { id: request.params.id }, where: { id: request.params.id },
data: { allowed: true } data: { allowed: true }
}); });
let allowScope: IAllowScope | undefined; let allowScope: IAllowScope | undefined;
allowScope = {kind: 'all'};
const body = request.body;
console.log({body});
// if (body.permissions === 'all') {
allowScope = {kind: 'all'};
// }
console.log({allowScope});
await allowAllRequestsFromKey( await allowAllRequestsFromKey(
record.remotePubkey, record.remotePubkey,
@ -64,82 +138,133 @@ export async function processRequestWebHandler(request, reply) {
allowScope allowScope
); );
if (record.method === "connect") {
debug("connect, adding sign_event capability");
await allowAllRequestsFromKey(
record.remotePubkey,
record.keyName,
"sign_event",
undefined,
undefined,
allowScope
);
}
return { ok: true }; return { ok: true };
} }
export async function processRegistrationWebHandler(request, reply) { export async function processRegistrationWebHandler(request, reply) {
const record = await prisma.request.findUnique({ try {
where: { id: request.params.id } const record = await getAndValidateStateOfRequest(request);
}); const body = request.body;
const body = request.body;
if (!record || record.allowed) { // we serialize the payload again and store it
return { ok: false, error: "Request not found or already processed" }; // along with the allowed flag
} // so that the original caller can get the current state
// to be processed
const payload: string[] = [];
payload.push(body.username);
payload.push(body.domain);
// we serialize the payload again and store it // TODO: validations here
// along with the allowed flag try {
// so that the original caller can get the current state await validateRegistration(request, record);
// to be processed } catch (e: any) {
const payload: string[] = []; const [ username, domain, email ] = JSON.parse(record.params!);
payload.push(body.username); const nip05 = `${username}@${domain}`;
payload.push(body.domain);
payload.push(body.email);
payload.push(body.password);
// TODO: validations here return reply.view("/templates/createAccount.handlebar", { record, email, username, domain, nip05, error: e.message});
await prisma.request.update({
where: { id: request.params.id },
data: { params: JSON.stringify(payload), allowed: true }
});
let createdPubkey: string | undefined;
// here I need to wait for the account
createdPubkey = await new Promise((resolve) => {
const interval = setInterval(async () => {
const keyName = record.keyName;
const keyRecord = await prisma.key.findUnique({ where: { keyName } });
if (keyRecord) {
console.log(keyRecord);
clearInterval(interval);
resolve(keyRecord.pubkey);
}
}, 100);
});
const callbackUrlString = body.callbackUrl;
let callbackUrl: string | undefined;
if (callbackUrlString) {
const u = new URL(callbackUrlString);
if (createdPubkey) {
u.searchParams.append("pubkey", createdPubkey);
callbackUrl = u.toString();
} }
await prisma.request.update({
where: { id: request.params.id },
data: { params: JSON.stringify(payload), allowed: true }
});
let createdPubkey: string | undefined;
// here I need to wait for the account
createdPubkey = await new Promise((resolve) => {
const interval = setInterval(async () => {
const keyName = record.keyName;
if (!keyName) throw new Error("Invalid keyName on generated account");
const keyRecord = await prisma.key.findUnique({ where: { keyName } });
if (keyRecord) {
console.log(keyRecord);
clearInterval(interval);
resolve(keyRecord.pubkey);
}
}, 100);
});
if (!createdPubkey) throw new Error("No pubkey found for keyName");
await createUserRecord(
body.username,
body.domain,
createdPubkey,
body.email,
body.password,
)
const callbackUrlString = body.callbackUrl;
let callbackUrl: string | undefined;
if (callbackUrlString) {
const u = new URL(callbackUrlString);
if (createdPubkey) {
u.searchParams.append("pubkey", createdPubkey);
callbackUrl = u.toString();
}
}
await allowAllRequestsFromKey(
record.remotePubkey,
record.keyName,
record.method,
undefined,
undefined,
);
// redirect to callbackUrl
if (callbackUrl) {
return reply
.view("/templates/redirect.handlebar", { callbackUrl })
.redirect(callbackUrl);
}
return reply.view("/templates/redirect.handlebar", { callbackUrl });
} catch (error: any) {
debug(`Error processing registration request`, error, request);
return reply.view("/templates/error.handlebar", { error: error.message });
} }
}
// const url = new URL(callbackUrl);
async function createUserRecord(
// add to url a query param with the user's pubkey username: string,
domain: string,
await allowAllRequestsFromKey( pubkey: string,
record.remotePubkey, email: string,
record.keyName, password: string,
record.method, ) {
undefined,
undefined, const hashedPassword = await bcrypt.hash(password, 10);
);
debug(`Creating user record for ${username}@${domain}`, {hashedPassword})
// redirect to login page
if (callbackUrl) { const userRecord = await prisma.user.create({
return reply data: {
.view("/templates/redirect.handlebar", { callbackUrl }) username,
.redirect(callbackUrl); domain,
} pubkey,
email,
return reply.view("/templates/redirect.handlebar", { callbackUrl }); password: hashedPassword,
}
});
return userRecord;
} }

View file

@ -0,0 +1,25 @@
import prisma from "../../db";
export async function validateRegistration(request, record) {
// validate username uniqueness
const body = request.body;
const { username, domain, email, password } = body;
const userRecord = await prisma.user.findUnique({
where: { username, domain }
});
if (userRecord) throw new Error("Username already exists. If this is your account, please login instead.");
// validate password length
if (password.length < 8) throw new Error("Password is too short");
// validate email (if present)
if (email) {
if (!email.includes("@")) throw new Error("Invalid email address");
// validate email uniqueness (if one was provided)
const emailRecord = await prisma.user.findUnique({ where: { email } });
if (emailRecord) throw new Error("Email already exists");
}
}

View file

@ -12,8 +12,10 @@
<script> <script>
function sendPostRequest(permissions) { function sendPostRequest(permissions) {
const url = '/requests/{{record.id}}'; const url = '/requests/{{record.id}}';
const password = document.getElementById('password').value;
const data = { const data = {
permissions permissions,
password
}; };
fetch(url, { fetch(url, {
@ -26,7 +28,16 @@
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
console.log('Success:', data); console.log('Success:', data);
window.close();
if (data.error) {
document.getElementById('error').innerText = data.error;
document.getElementById('error').classList.remove('hidden');
return;
}
// hide main content and show close message
document.getElementById('main').classList.add('hidden');
document.getElementById('closeit').classList.remove('hidden');
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error('Error:', error);
@ -55,14 +66,40 @@
</head> </head>
<body class="flex flex-col items-center justify-center min-h-screen min-w-screen px-10"> <body class="flex flex-col items-center justify-center min-h-screen min-w-screen px-10">
<div class="flex justify-center mb-6"> <div class="flex justify-center mb-6">
<h1 class="text-4xl font-black text-center text-primary w-full">
<svg id="_8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 927.56 361.95" class="w-full px-20"><defs><style>.cls-1{fill:none;}.cls-2{fill:#662482;}</style></defs><path class="cls-1" d="m72.63,0h782.3c40.09,0,72.63,32.54,72.63,72.63v216.69c0,40.09-32.54,72.63-72.63,72.63H72.63c-40.09,0-72.63-32.54-72.63-72.63V72.63C0,32.54,32.54,0,72.63,0Z"/><path class="cls-2" d="m857.69,133.61c-16.21.9-29.75,5.48-39.96,13.54-3.97,3.13-9.76.57-9.76-4.49v-.88c0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.27,0,5.92-2.65,5.92-5.92v-70.7c0-31.07,17.2-45.25,43.14-45.25,1.51,0,2.91.05,4.32.13,3.39.2,6.24-2.51,6.24-5.91v-30.98c0-3.34-2.76-6.11-6.09-5.92Z"/><path class="cls-2" d="m728.78,263.61c-1.24-3.25-4.94-4.53-8.13-3.14-4.09,1.79-8.71,2.72-13.34,2.72-12.67,0-20.21-7.54-20.21-21.42v-96.41c0-3.24,2.62-5.86,5.86-5.86h28.7c3.24,0,5.86-2.62,5.86-5.86v-24.48c0-3.24-2.62-5.86-5.86-5.86h-28.7c-3.24,0-5.86-2.62-5.86-5.86v-27.8c0-3.24-2.62-5.86-5.86-5.86h-35.34c-3.24,0-5.86,2.62-5.86,5.86v27.8c0,3.24-2.62,5.86-5.86,5.86h-13.31c-3.24,0-5.86,2.62-5.86,5.86v24.48c0,3.24,2.62,5.86,5.86,5.86h13.31c3.24,0,5.86,2.62,5.86,5.86v97.01c0,38.62,22.33,58.23,60.64,58.23,12.38,0,24.54-2.42,33.84-7.47,2.62-1.42,3.8-4.54,2.74-7.32l-8.48-22.21Z"/><path class="cls-2" d="m504.8,184.13c0-8.45,9.05-15.09,30.17-15.09,13.14,0,27.23,2.39,41.43,9.27,2.95,1.43,6.49.32,7.88-2.65l10.73-22.9c1.4-2.98.11-6.5-2.86-7.91-15.41-7.32-37.26-11.4-57.19-11.4-47.37,0-75.12,21.72-75.12,52.49,0,64.87,98.05,37.71,98.05,64.26,0,9.05-8.14,14.79-29.87,14.79-17.58,0-36.81-4.94-51.05-12.54-2.99-1.6-6.68-.35-8.1,2.73l-10.7,23.04c-1.33,2.86-.22,6.24,2.55,7.74,15.73,8.5,40.73,14.63,65.48,14.63,48.57,0,76.63-21.42,76.63-51.59,0-63.96-98.05-37.11-98.05-64.86Z"/><path class="cls-2" d="m351.26,133.45c-51.59,0-89.3,34.69-89.3,83.57s37.71,83.57,89.3,83.57,89-34.69,89-83.57-37.41-83.57-89-83.57Zm42.94,109.73c-6.48,24.4-31.8,38.86-56.55,32.29-24.75-6.57-39.57-31.68-33.09-56.08,6.48-24.4,39.62-68.32,64.37-61.74,24.75,6.57,31.75,61.14,25.27,85.54Z"/><path class="cls-2" d="m162.43,133.45c-17.1,0-32.38,4.55-44.29,13.07-3.95,2.83-9.41.12-9.41-4.74,0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.23,0,5.84-2.6,5.9-5.82v-8.76c0-65.62-34.67-114.78-.38-121.3,31.37-5.98,66.98-3.09,67.4,21.06.04,2.09.32,8.34,8.99,11.75,5.18,2.04,13.16,2.75,23.65,2.48,0,0,9.51-.76,9.51,8.94,0,12.06-21.29,11.21-21.29,11.21-7.05.33-23.65-1.61-33.11,1.23-5.01,1.51-9.36,4.35-12.01,9.55-4.41,8.65-6.51,27.67-6.84,47.54v16.2c0,3.27,2.65,5.92,5.92,5.92h71.12c3.27,0,5.92-2.65,5.92-5.92v-87c0-49.47-28.96-71.8-67.28-71.8Z"/><path class="cls-2" d="m166.12,187.59c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/><path class="cls-2" d="m229.71,205.25v87c0,3.27-2.65,5.92-5.92,5.92h-71.12c-3.27,0-5.92-2.65-5.92-5.92v-16.2c.33-19.87,2.43-38.89,6.84-47.54,2.65-5.2,7-8.04,12.01-9.55,9.46-2.84,26.06-.9,33.11-1.23,0,0,21.29.85,21.29-11.21,0-9.7-9.51-8.94-9.51-8.94-10.49.27-18.47-.44-23.65-2.48-8.67-3.41-8.95-9.66-8.99-11.75-.42-24.15-36.03-27.04-67.4-21.06-34.29,6.53.38,55.68.38,121.3v8.76c-.06,3.22-2.67,5.82-5.9,5.82h-35.22c-3.27,0-5.92-2.65-5.92-5.92v-150.46c0-3.27,2.65-5.92,5.92-5.92h33.11c3.27,0,5.92,2.65,5.92,5.92,0,4.86,5.46,7.57,9.41,4.74,11.91-8.53,27.19-13.07,44.29-13.07,38.31,0,67.28,22.33,67.28,71.8Zm-63.59-17.66c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/></svg>
</h1>
</div> </div>
<h1 class="text-center text-2xl font-semibold">Do you want to allow this client to use account <div id="main">
<br/> <h1 class="text-center text-2xl font-semibold">Do you want to allow this client to use account
<span class="font-black">{{record.keyName}}</span>?</h1> <br/>
<div class="flex flex-row items-center justify-center gap-8 mt-8"> <span class="font-black">{{record.keyName}}</span>?</h1>
<button onclick="sendPostRequest()" class="py-3 bg-black hover:bg-neutral-700 transition-all duration-300 text-lg rounded-lg px-10 justify-center items-center gap-2 inline-flex text-white font-semibold">Yes</button>
<button onclick="window.close()" class="text-lg px-10 bg-neutral-200 hover:!bg-neutral-300 transition-all duration-200 rounded-lg py-3">No</button> <div id="error" class="flex flex-col gap-4 bg-red-200 rounded-lg p-4 w-full hidden">
</div>
{{#unless authenticated}}
<div class="flex flex-col gap-4 mt-8">
<div class="flex flex-col gap-2 bg-neutral-200 rounded-lg p-4 w-full">
<span>Enter your password to authenticate this request</span>
<input type="password" id="password" name="password" class="border border-gray-300 rounded-lg px-4 py-2" placeholder="Password" />
</div>
</div>
{{/unless}}
<div class="flex flex-row items-center justify-center gap-8 mt-8">
<button onclick="sendPostRequest()" class="py-3 bg-black hover:bg-neutral-700 transition-all duration-300 text-lg rounded-lg px-10 justify-center items-center gap-2 inline-flex text-white font-semibold">Yes</button>
<button onclick="window.close()" class="text-lg px-10 bg-neutral-200 hover:!bg-neutral-300 transition-all duration-200 rounded-lg py-3">No</button>
</div>
</div>
<div class="hidden" id="closeit">
<div class="flex justify-center mb-6">
<p class="text-center text-gray-600">
You can close this window now.
</p>
</div>
</div> </div>
<!-- List all cookies --> <!-- List all cookies -->

View file

@ -8,6 +8,41 @@
<link rel="preconnect" href="https://rsms.me/" /> <link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const form = document.querySelector('form');
const email = document.querySelector('input[name="email"]');
const password = document.querySelector('input[name="password"]');
const confirmPassword = document.querySelector('input[name="confirm_password"]');
form.addEventListener('submit', function(event) {
let valid = true;
// Check if passwords match and are at least 8 characters long
if (password.value !== confirmPassword.value) {
alert("Passwords do not match!");
valid = false;
} else if (password.value.length < 8) {
alert("Password must be at least 8 characters long!");
valid = false;
}
// Check if email is valid or empty
if (email.value) {
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
if (!emailRegex.test(email.value)) {
alert("Please enter a valid email address or leave it empty!");
valid = false;
}
}
if (!valid) {
event.preventDefault();
}
});
});
</script>
<style> <style>
html { html {
@ -39,9 +74,15 @@
</div> </div>
<form action="/register/{{record.id}}" method="POST" class="flex flex-col gap-2"> <form action="/register/{{record.id}}" method="POST" class="flex flex-col gap-2">
{{#if error}}
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{error}}</span>
</div>
{{/if}}
<label> <label>
<span>Recovery Email</span> <span>Recovery Email</span>
<input type="email" name="email" value="{{email}}" required class="w-full px-4 py-3 bg-white rounded-lg shadow border border-neutral-200 justify-start items-center gap-2 inline-flex" /> <input type="email" name="email" value="{{email}}" class="w-full px-4 py-3 bg-white rounded-lg shadow border border-neutral-200 justify-start items-center gap-2 inline-flex" />
</label> </label>
<label> <label>

33
templates/error.handlebar Normal file
View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Nostr</title>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<script src="https://cdn.tailwindcss.com"></script>
<style>
html {
font-family: Inter, sans-serif;
}
label > span {
font-weight: 600;
}
</style>
</head>
<body class="p-2 flex flex-col justify-center items-center min-h-screen bg-gray-100">
<div class="max-w-lg w-full">
<div class="flex justify-center mb-6">
<h1 class="text-4xl font-black text-center text-primary w-full">
<svg id="_8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 927.56 361.95" class="w-full px-20"><defs><style>.cls-1{fill:none;}.cls-2{fill:#662482;}</style></defs><path class="cls-1" d="m72.63,0h782.3c40.09,0,72.63,32.54,72.63,72.63v216.69c0,40.09-32.54,72.63-72.63,72.63H72.63c-40.09,0-72.63-32.54-72.63-72.63V72.63C0,32.54,32.54,0,72.63,0Z"/><path class="cls-2" d="m857.69,133.61c-16.21.9-29.75,5.48-39.96,13.54-3.97,3.13-9.76.57-9.76-4.49v-.88c0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.27,0,5.92-2.65,5.92-5.92v-70.7c0-31.07,17.2-45.25,43.14-45.25,1.51,0,2.91.05,4.32.13,3.39.2,6.24-2.51,6.24-5.91v-30.98c0-3.34-2.76-6.11-6.09-5.92Z"/><path class="cls-2" d="m728.78,263.61c-1.24-3.25-4.94-4.53-8.13-3.14-4.09,1.79-8.71,2.72-13.34,2.72-12.67,0-20.21-7.54-20.21-21.42v-96.41c0-3.24,2.62-5.86,5.86-5.86h28.7c3.24,0,5.86-2.62,5.86-5.86v-24.48c0-3.24-2.62-5.86-5.86-5.86h-28.7c-3.24,0-5.86-2.62-5.86-5.86v-27.8c0-3.24-2.62-5.86-5.86-5.86h-35.34c-3.24,0-5.86,2.62-5.86,5.86v27.8c0,3.24-2.62,5.86-5.86,5.86h-13.31c-3.24,0-5.86,2.62-5.86,5.86v24.48c0,3.24,2.62,5.86,5.86,5.86h13.31c3.24,0,5.86,2.62,5.86,5.86v97.01c0,38.62,22.33,58.23,60.64,58.23,12.38,0,24.54-2.42,33.84-7.47,2.62-1.42,3.8-4.54,2.74-7.32l-8.48-22.21Z"/><path class="cls-2" d="m504.8,184.13c0-8.45,9.05-15.09,30.17-15.09,13.14,0,27.23,2.39,41.43,9.27,2.95,1.43,6.49.32,7.88-2.65l10.73-22.9c1.4-2.98.11-6.5-2.86-7.91-15.41-7.32-37.26-11.4-57.19-11.4-47.37,0-75.12,21.72-75.12,52.49,0,64.87,98.05,37.71,98.05,64.26,0,9.05-8.14,14.79-29.87,14.79-17.58,0-36.81-4.94-51.05-12.54-2.99-1.6-6.68-.35-8.1,2.73l-10.7,23.04c-1.33,2.86-.22,6.24,2.55,7.74,15.73,8.5,40.73,14.63,65.48,14.63,48.57,0,76.63-21.42,76.63-51.59,0-63.96-98.05-37.11-98.05-64.86Z"/><path class="cls-2" d="m351.26,133.45c-51.59,0-89.3,34.69-89.3,83.57s37.71,83.57,89.3,83.57,89-34.69,89-83.57-37.41-83.57-89-83.57Zm42.94,109.73c-6.48,24.4-31.8,38.86-56.55,32.29-24.75-6.57-39.57-31.68-33.09-56.08,6.48-24.4,39.62-68.32,64.37-61.74,24.75,6.57,31.75,61.14,25.27,85.54Z"/><path class="cls-2" d="m162.43,133.45c-17.1,0-32.38,4.55-44.29,13.07-3.95,2.83-9.41.12-9.41-4.74,0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.23,0,5.84-2.6,5.9-5.82v-8.76c0-65.62-34.67-114.78-.38-121.3,31.37-5.98,66.98-3.09,67.4,21.06.04,2.09.32,8.34,8.99,11.75,5.18,2.04,13.16,2.75,23.65,2.48,0,0,9.51-.76,9.51,8.94,0,12.06-21.29,11.21-21.29,11.21-7.05.33-23.65-1.61-33.11,1.23-5.01,1.51-9.36,4.35-12.01,9.55-4.41,8.65-6.51,27.67-6.84,47.54v16.2c0,3.27,2.65,5.92,5.92,5.92h71.12c3.27,0,5.92-2.65,5.92-5.92v-87c0-49.47-28.96-71.8-67.28-71.8Z"/><path class="cls-2" d="m166.12,187.59c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/><path class="cls-2" d="m229.71,205.25v87c0,3.27-2.65,5.92-5.92,5.92h-71.12c-3.27,0-5.92-2.65-5.92-5.92v-16.2c.33-19.87,2.43-38.89,6.84-47.54,2.65-5.2,7-8.04,12.01-9.55,9.46-2.84,26.06-.9,33.11-1.23,0,0,21.29.85,21.29-11.21,0-9.7-9.51-8.94-9.51-8.94-10.49.27-18.47-.44-23.65-2.48-8.67-3.41-8.95-9.66-8.99-11.75-.42-24.15-36.03-27.04-67.4-21.06-34.29,6.53.38,55.68.38,121.3v8.76c-.06,3.22-2.67,5.82-5.9,5.82h-35.22c-3.27,0-5.92-2.65-5.92-5.92v-150.46c0-3.27,2.65-5.92,5.92-5.92h33.11c3.27,0,5.92,2.65,5.92,5.92,0,4.86,5.46,7.57,9.41,4.74,11.91-8.53,27.19-13.07,44.29-13.07,38.31,0,67.28,22.33,67.28,71.8Zm-63.59-17.66c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/></svg>
Login to Nostr
</h1>
</div>
<div id="error" class="flex flex-col gap-4 bg-red-200 rounded-lg p-4 w-full">
{{error}}
</div>
</div>
</body>
</html>

49
templates/login.handlebar Normal file
View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Nostr</title>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<script src="https://cdn.tailwindcss.com"></script>
<style>
html {
font-family: Inter, sans-serif;
}
label > span {
font-weight: 600;
}
</style>
</head>
<body class="p-2 flex flex-col justify-center items-center min-h-screen bg-gray-100">
<div class="max-w-lg w-full">
<div class="flex justify-center mb-6">
<h1 class="text-4xl font-black text-center text-primary w-full">
<svg id="_8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 927.56 361.95" class="w-full px-20"><defs><style>.cls-1{fill:none;}.cls-2{fill:#662482;}</style></defs><path class="cls-1" d="m72.63,0h782.3c40.09,0,72.63,32.54,72.63,72.63v216.69c0,40.09-32.54,72.63-72.63,72.63H72.63c-40.09,0-72.63-32.54-72.63-72.63V72.63C0,32.54,32.54,0,72.63,0Z"/><path class="cls-2" d="m857.69,133.61c-16.21.9-29.75,5.48-39.96,13.54-3.97,3.13-9.76.57-9.76-4.49v-.88c0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.27,0,5.92-2.65,5.92-5.92v-70.7c0-31.07,17.2-45.25,43.14-45.25,1.51,0,2.91.05,4.32.13,3.39.2,6.24-2.51,6.24-5.91v-30.98c0-3.34-2.76-6.11-6.09-5.92Z"/><path class="cls-2" d="m728.78,263.61c-1.24-3.25-4.94-4.53-8.13-3.14-4.09,1.79-8.71,2.72-13.34,2.72-12.67,0-20.21-7.54-20.21-21.42v-96.41c0-3.24,2.62-5.86,5.86-5.86h28.7c3.24,0,5.86-2.62,5.86-5.86v-24.48c0-3.24-2.62-5.86-5.86-5.86h-28.7c-3.24,0-5.86-2.62-5.86-5.86v-27.8c0-3.24-2.62-5.86-5.86-5.86h-35.34c-3.24,0-5.86,2.62-5.86,5.86v27.8c0,3.24-2.62,5.86-5.86,5.86h-13.31c-3.24,0-5.86,2.62-5.86,5.86v24.48c0,3.24,2.62,5.86,5.86,5.86h13.31c3.24,0,5.86,2.62,5.86,5.86v97.01c0,38.62,22.33,58.23,60.64,58.23,12.38,0,24.54-2.42,33.84-7.47,2.62-1.42,3.8-4.54,2.74-7.32l-8.48-22.21Z"/><path class="cls-2" d="m504.8,184.13c0-8.45,9.05-15.09,30.17-15.09,13.14,0,27.23,2.39,41.43,9.27,2.95,1.43,6.49.32,7.88-2.65l10.73-22.9c1.4-2.98.11-6.5-2.86-7.91-15.41-7.32-37.26-11.4-57.19-11.4-47.37,0-75.12,21.72-75.12,52.49,0,64.87,98.05,37.71,98.05,64.26,0,9.05-8.14,14.79-29.87,14.79-17.58,0-36.81-4.94-51.05-12.54-2.99-1.6-6.68-.35-8.1,2.73l-10.7,23.04c-1.33,2.86-.22,6.24,2.55,7.74,15.73,8.5,40.73,14.63,65.48,14.63,48.57,0,76.63-21.42,76.63-51.59,0-63.96-98.05-37.11-98.05-64.86Z"/><path class="cls-2" d="m351.26,133.45c-51.59,0-89.3,34.69-89.3,83.57s37.71,83.57,89.3,83.57,89-34.69,89-83.57-37.41-83.57-89-83.57Zm42.94,109.73c-6.48,24.4-31.8,38.86-56.55,32.29-24.75-6.57-39.57-31.68-33.09-56.08,6.48-24.4,39.62-68.32,64.37-61.74,24.75,6.57,31.75,61.14,25.27,85.54Z"/><path class="cls-2" d="m162.43,133.45c-17.1,0-32.38,4.55-44.29,13.07-3.95,2.83-9.41.12-9.41-4.74,0-3.27-2.65-5.92-5.92-5.92h-33.11c-3.27,0-5.92,2.65-5.92,5.92v150.46c0,3.27,2.65,5.92,5.92,5.92h35.22c3.23,0,5.84-2.6,5.9-5.82v-8.76c0-65.62-34.67-114.78-.38-121.3,31.37-5.98,66.98-3.09,67.4,21.06.04,2.09.32,8.34,8.99,11.75,5.18,2.04,13.16,2.75,23.65,2.48,0,0,9.51-.76,9.51,8.94,0,12.06-21.29,11.21-21.29,11.21-7.05.33-23.65-1.61-33.11,1.23-5.01,1.51-9.36,4.35-12.01,9.55-4.41,8.65-6.51,27.67-6.84,47.54v16.2c0,3.27,2.65,5.92,5.92,5.92h71.12c3.27,0,5.92-2.65,5.92-5.92v-87c0-49.47-28.96-71.8-67.28-71.8Z"/><path class="cls-2" d="m166.12,187.59c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/><path class="cls-2" d="m229.71,205.25v87c0,3.27-2.65,5.92-5.92,5.92h-71.12c-3.27,0-5.92-2.65-5.92-5.92v-16.2c.33-19.87,2.43-38.89,6.84-47.54,2.65-5.2,7-8.04,12.01-9.55,9.46-2.84,26.06-.9,33.11-1.23,0,0,21.29.85,21.29-11.21,0-9.7-9.51-8.94-9.51-8.94-10.49.27-18.47-.44-23.65-2.48-8.67-3.41-8.95-9.66-8.99-11.75-.42-24.15-36.03-27.04-67.4-21.06-34.29,6.53.38,55.68.38,121.3v8.76c-.06,3.22-2.67,5.82-5.9,5.82h-35.22c-3.27,0-5.92-2.65-5.92-5.92v-150.46c0-3.27,2.65-5.92,5.92-5.92h33.11c3.27,0,5.92,2.65,5.92,5.92,0,4.86,5.46,7.57,9.41,4.74,11.91-8.53,27.19-13.07,44.29-13.07,38.31,0,67.28,22.33,67.28,71.8Zm-63.59-17.66c0-7-5.67-12.67-12.67-12.67s-12.67,5.68-12.67,12.67,5.67,12.68,12.67,12.68,12.67-5.68,12.67-12.68Z"/></svg>
Login to Nostr
</h1>
</div>
<form action="/login" method="POST" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">
<span>Username</span>
<input type="text" name="username" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required value={{nip05}}>
</label>
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2">
<span>Password</span>
<input type="password" name="password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" required>
</label>
</div>
<div class="flex items-center justify-between">
<button type="submit" class="bg-black text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Log In
</button>
</div>
</form>
</div>
</body>
</html>

View file

@ -68,6 +68,7 @@
<script> <script>
// If callbackUrl is not provided, close the window // If callbackUrl is not provided, close the window
if (!"{{callbackUrl}}") { if (!"{{callbackUrl}}") {
window.close();
} else { } else {
// If callbackUrl is provided, redirect to it // If callbackUrl is provided, redirect to it
window.location.href = "{{callbackUrl}}"; window.location.href = "{{callbackUrl}}";