diff --git a/package-lock.json b/package-lock.json index da1cade..a9fb01a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "jszip": "3.10.1", "lodash": "4.17.21", "mui-file-input": "4.0.4", + "nostr-login": "^1.6.6", "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", @@ -1856,9 +1857,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], @@ -1870,9 +1871,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], @@ -1884,9 +1885,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], @@ -1898,9 +1899,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], @@ -1912,9 +1913,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", "cpu": [ "arm" ], @@ -1926,9 +1927,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], @@ -1940,9 +1941,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], @@ -1954,9 +1955,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], @@ -1968,9 +1969,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", "cpu": [ "ppc64" ], @@ -1982,9 +1983,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", "cpu": [ "riscv64" ], @@ -1996,9 +1997,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "cpu": [ "s390x" ], @@ -2010,9 +2011,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], @@ -2024,9 +2025,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], @@ -2038,9 +2039,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], @@ -2052,9 +2053,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], @@ -2066,9 +2067,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], @@ -5277,6 +5278,72 @@ "node": ">=0.10.0" } }, + "node_modules/nostr-login": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/nostr-login/-/nostr-login-1.6.6.tgz", + "integrity": "sha512-XOpB9nG3Qgt7iea7gA1zn4TaTfUKCKGdCHKwErqLPtMk/q1Rhkzj5cq/66iU0WqC6mSiwENfTy1p4qaM7HzMtg==", + "license": "MIT", + "dependencies": { + "@nostr-dev-kit/ndk": "^2.3.1", + "nostr-tools": "^1.17.0", + "tseep": "^1.2.1" + } + }, + "node_modules/nostr-login/node_modules/@noble/ciphers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-login/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-login/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-login/node_modules/nostr-tools": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.17.0.tgz", + "integrity": "sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/nostr-tools": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.0.tgz", @@ -5586,9 +5653,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true, "license": "ISC" }, @@ -6215,9 +6282,9 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dev": true, "license": "MIT", "dependencies": { @@ -6231,22 +6298,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -6975,9 +7042,9 @@ } }, "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a833103..6cedc9b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sigit", "private": true, - "version": "0.0.0", + "version": "0.0.0-beta", "type": "module", "homepage": "https://sigit.io/", "license": "AGPL-3.0-or-later ", @@ -42,6 +42,7 @@ "jszip": "3.10.1", "lodash": "4.17.21", "mui-file-input": "4.0.4", + "nostr-login": "^1.6.6", "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", diff --git a/src/App.tsx b/src/App.tsx index 1523e6f..5dbb867 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' -import { useSelector } from 'react-redux' +import { useAppSelector } from './hooks/store' import { Navigate, Route, Routes } from 'react-router-dom' -import { AuthController, NostrController } from './controllers' +import { AuthController } from './controllers' import { MainLayout } from './layouts/Main' import { appPrivateRoutes, @@ -10,12 +10,10 @@ import { publicRoutes, recursiveRouteRenderer } from './routes' -import { State } from './store/rootReducer' -import { getNsecBunkerDelegatedKey, saveNsecBunkerDelegatedKey } from './utils' import './App.scss' const App = () => { - const authState = useSelector((state: State) => state.auth) + const authState = useAppSelector((state) => state.auth) useEffect(() => { if (window.location.hostname === '0.0.0.0') { @@ -25,23 +23,10 @@ const App = () => { window.location.hostname = 'localhost' } - generateBunkerDelegatedKey() - const authController = new AuthController() authController.checkSession() }, []) - const generateBunkerDelegatedKey = () => { - const existingKey = getNsecBunkerDelegatedKey() - - if (!existingKey) { - const nostrController = NostrController.getInstance() - const newDelegatedKey = nostrController.generateDelegatedKey() - - saveNsecBunkerDelegatedKey(newDelegatedKey) - } - } - const handleRootRedirect = () => { if (authState.loggedIn) return appPrivateRoutes.homePage const callbackPathEncoded = btoa( diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx index ff68469..6a5862d 100644 --- a/src/components/AppBar/AppBar.tsx +++ b/src/components/AppBar/AppBar.tsx @@ -9,51 +9,36 @@ import { } from '@mui/material' import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { - setAuthState, - setMetadataEvent, - userLogOutAction -} from '../../store/actions' -import { State } from '../../store/rootReducer' -import { Dispatch } from '../../store/store' +import { useAppSelector } from '../../hooks/store' import Username from '../username' import { Link, useNavigate } from 'react-router-dom' -import { MetadataController, NostrController } from '../../controllers' import { appPublicRoutes, appPrivateRoutes, getProfileRoute } from '../../routes' -import { - clearAuthToken, - getProfileUsername, - hexToNpub, - saveNsecBunkerDelegatedKey -} from '../../utils' +import { getProfileUsername, hexToNpub } from '../../utils' import styles from './style.module.scss' -import { setUserRobotImage } from '../../store/userRobotImage/action' import { Container } from '../Container' import { ButtonIcon } from '../ButtonIcon' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faClose } from '@fortawesome/free-solid-svg-icons' import useMediaQuery from '@mui/material/useMediaQuery' +import { useLogout } from '../../hooks/useLogout' -const metadataController = MetadataController.getInstance() +import { launch as launchNostrLoginDialog } from 'nostr-login' export const AppBar = () => { const navigate = useNavigate() - - const dispatch: Dispatch = useDispatch() - + const logout = useLogout() const [username, setUsername] = useState('') const [userAvatar, setUserAvatar] = useState('') const [anchorElUser, setAnchorElUser] = useState(null) - const authState = useSelector((state: State) => state.auth) - const metadataState = useSelector((state: State) => state.metadata) - const userRobotImage = useSelector((state: State) => state.userRobotImage) + const authState = useAppSelector((state) => state.auth) + const metadataState = useAppSelector((state) => state.metadata) + const userRobotImage = useAppSelector((state) => state.userRobotImage) useEffect(() => { if (metadataState) { @@ -94,28 +79,7 @@ export const AppBar = () => { const handleLogout = () => { handleCloseUserMenu() - dispatch( - setAuthState({ - keyPair: undefined, - loggedIn: false, - usersPubkey: undefined, - loginMethod: undefined, - nsecBunkerPubkey: undefined - }) - ) - dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent())) - dispatch(setUserRobotImage(null)) - - // clear authToken saved in local storage - clearAuthToken() - - dispatch(userLogOutAction()) - - // update nsecBunker delegated key after logout - const nostrController = NostrController.getInstance() - const newDelegatedKey = nostrController.generateDelegatedKey() - saveNsecBunkerDelegatedKey(newDelegatedKey) - + logout() navigate('/') } const isAuthenticated = authState?.loggedIn === true @@ -133,7 +97,7 @@ export const AppBar = () => {

- SIGit is currently Alpha software (available for internal + SIGit is currently Beta software (available for user experience testing), use at your own risk!

diff --git a/src/components/MarkFormField/style.module.scss b/src/components/MarkFormField/style.module.scss index 9f4b092..0125140 100644 --- a/src/components/MarkFormField/style.module.scss +++ b/src/components/MarkFormField/style.module.scss @@ -15,7 +15,7 @@ left: 5px; align-items: center; - z-index: 1000; + z-index: 40; button { transition: ease 0.2s; diff --git a/src/components/PDFView/style.module.scss b/src/components/PDFView/style.module.scss index 870057a..61983d7 100644 --- a/src/components/PDFView/style.module.scss +++ b/src/components/PDFView/style.module.scss @@ -6,7 +6,7 @@ .otherUserMarksDisplay { position: absolute; - z-index: 50; + z-index: 40; display: flex; justify-content: center; align-items: center; diff --git a/src/components/UsersDetails.tsx/index.tsx b/src/components/UsersDetails.tsx/index.tsx index 3a5c358..778a3d9 100644 --- a/src/components/UsersDetails.tsx/index.tsx +++ b/src/components/UsersDetails.tsx/index.tsx @@ -20,8 +20,7 @@ import { faFileCircleExclamation } from '@fortawesome/free-solid-svg-icons' import { getExtensionIconLabel } from '../getExtensionIconLabel' -import { useSelector } from 'react-redux' -import { State } from '../../store/rootReducer' +import { useAppSelector } from '../../hooks/store' import { DisplaySigner } from '../DisplaySigner' import { Meta } from '../../types' import { extractFileExtensions } from '../../utils/file' @@ -45,7 +44,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { signedStatus, isValid } = useSigitMeta(meta) - const { usersPubkey } = useSelector((state: State) => state.auth) + const { usersPubkey } = useAppSelector((state) => state.auth) const userCanSign = typeof usersPubkey !== 'undefined' && signers.includes(hexToNpub(usersPubkey)) diff --git a/src/components/username.tsx b/src/components/username.tsx index 7ee6dc3..e84a3d8 100644 --- a/src/components/username.tsx +++ b/src/components/username.tsx @@ -1,6 +1,5 @@ import { Typography } from '@mui/material' -import { useSelector } from 'react-redux' -import { State } from '../store/rootReducer' +import { useAppSelector } from '../hooks/store' import styles from './username.module.scss' import { AvatarIconButton } from './UserAvatarIconButton' @@ -16,7 +15,7 @@ type Props = { * Clicking will open the menu. */ const Username = ({ username, avatarContent, handleClick }: Props) => { - const hexKey = useSelector((state: State) => state.auth.usersPubkey) + const hexKey = useAppSelector((state) => state.auth.usersPubkey) return (
diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index cc8def5..6536e19 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -31,7 +31,7 @@ export class AuthController { /** * Function will authenticate user by signing an auth event * which is done by calling the sign() function, where appropriate - * method will be chosen (extension, nsecbunker or keys) + * method will be chosen (extension or keys) * * @param pubkey of the user trying to login * @returns url to redirect if authentication successfull @@ -57,12 +57,15 @@ export class AuthController { // Nostr uses unix timestamps const timestamp = unixNow() - const { hostname } = window.location + const { href } = window.location const authEvent: EventTemplate = { kind: 27235, - tags: [], - content: `${hostname}-${timestamp}`, + tags: [ + ['u', href], + ['method', 'GET'] + ], + content: '', created_at: timestamp } @@ -83,7 +86,7 @@ export class AuthController { return Promise.resolve(appPrivateRoutes.relays) } - if (store.getState().auth?.loggedIn) { + if (store.getState().auth.loggedIn) { if (!compareObjects(store.getState().relays?.map, relayMap.map)) store.dispatch(setRelayMapAction(relayMap.map)) } diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 0547ffb..c3d2908 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -1,194 +1,24 @@ -import NDK, { - NDKEvent, - NDKNip46Signer, - NDKPrivateKeySigner, - NDKUser, - NostrEvent -} from '@nostr-dev-kit/ndk' -import { - Event, - EventTemplate, - UnsignedEvent, - finalizeEvent, - nip04, - nip19, - nip44 -} from 'nostr-tools' +import { EventTemplate, UnsignedEvent } from 'nostr-tools' +import { WindowNostr } from 'nostr-tools/nip07' import { EventEmitter } from 'tseep' -import { updateNsecbunkerPubkey } from '../store/actions' -import { AuthState, LoginMethods } from '../store/auth/types' import store from '../store/store' import { SignedEvent } from '../types' -import { getNsecBunkerDelegatedKey, verifySignedEvent } from '../utils' +import { LoginMethodContext } from '../services/LoginMethodStrategy/loginMethodContext' export class NostrController extends EventEmitter { private static instance: NostrController - private bunkerNDK: NDK | undefined - private remoteSigner: NDKNip46Signer | undefined - private constructor() { super() } - private getNostrObject = () => { - // fix: this is not picking up type declaration from src/system/index.d.ts - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (window.nostr) return window.nostr as any + if (window.nostr) return window.nostr as WindowNostr throw new Error( `window.nostr object not present. Make sure you have an nostr extension installed/working properly.` ) } - public nsecBunkerInit = async (relays: string[]) => { - // Don't reinstantiate bunker NDK if exists with same relays - if ( - this.bunkerNDK && - this.bunkerNDK.explicitRelayUrls?.length === relays.length && - this.bunkerNDK.explicitRelayUrls?.every((relay) => relays.includes(relay)) - ) - return - - this.bunkerNDK = new NDK({ - explicitRelayUrls: relays - }) - - try { - await this.bunkerNDK - .connect(2000) - .then(() => { - console.log( - `Successfully connected to the nsecBunker relays: ${relays.join( - ',' - )}` - ) - }) - .catch((err) => { - console.error( - `Error connecting to the nsecBunker relays: ${relays.join( - ',' - )} ${err}` - ) - }) - } catch (err) { - console.error(err) - } - } - - /** - * Creates nSecBunker signer instance for the given npub - * Or if npub omitted it will return existing signer - * If neither, error will be thrown - * @param npub nPub / public key in hex format - * @returns nsecBunker Signer instance - */ - public createNsecBunkerSigner = async ( - npub: string | undefined - ): Promise => { - const nsecBunkerDelegatedKey = getNsecBunkerDelegatedKey() - - return new Promise((resolve, reject) => { - if (!nsecBunkerDelegatedKey) { - reject('nsecBunker delegated key is not found in the browser.') - return - } - const localSigner = new NDKPrivateKeySigner(nsecBunkerDelegatedKey) - - if (!npub) { - if (this.remoteSigner) resolve(this.remoteSigner) - - const npubFromStorage = (store.getState().auth as AuthState) - .nsecBunkerPubkey - - if (npubFromStorage) { - npub = npubFromStorage - } else { - reject( - 'No signer instance present, no npub provided by user or found in the browser.' - ) - return - } - } else { - store.dispatch(updateNsecbunkerPubkey(npub)) - } - - // Pubkey of a key pair stored in nsecbunker that will be used to sign event with - const appPubkeyOrToken = npub.includes('npub') - ? npub - : nip19.npubEncode(npub) - - /** - * When creating and NDK instance we create new connection to the relay - * To prevent too much connections and hitting rate limits, if npub against which we sign - * we will reuse existing instance. Otherwise we will create new NDK and signer instance. - */ - if (!this.remoteSigner || this.remoteSigner?.remotePubkey !== npub) { - this.remoteSigner = new NDKNip46Signer( - this.bunkerNDK!, - appPubkeyOrToken, - localSigner - ) - } - - /** - * when nsecbunker-delegated-key is regenerated we have to reinitialize the remote signer - */ - if (this.remoteSigner.localSigner !== localSigner) { - this.remoteSigner = new NDKNip46Signer( - this.bunkerNDK!, - appPubkeyOrToken, - localSigner - ) - } - - resolve(this.remoteSigner) - }) - } - - /** - * Signs the nostr event and returns the sig and id or full raw nostr event - * @param npub stored in nsecBunker to sign with - * @param event to be signed - * @param returnFullEvent whether to return full raw nostr event or just SIG and ID values - */ - public signWithNsecBunker = async ( - npub: string | undefined, - event: NostrEvent, - returnFullEvent = true - ): Promise<{ id: string; sig: string } | NostrEvent> => { - return new Promise((resolve, reject) => { - this.createNsecBunkerSigner(npub) - .then(async (signer) => { - const ndkEvent = new NDKEvent(undefined, event) - - const timeout = setTimeout(() => { - reject('Timeout occurred while waiting for event signing') - }, 60000) // 60000 ms (1 min) = 1000 * 60 - - await ndkEvent.sign(signer).catch((err) => { - clearTimeout(timeout) - reject(err) - return - }) - - clearTimeout(timeout) - - if (returnFullEvent) { - resolve(ndkEvent.rawEvent()) - } else { - resolve({ - id: ndkEvent.id, - sig: ndkEvent.sig! - }) - } - }) - .catch((err) => { - reject(err) - }) - }) - } - public static getInstance(): NostrController { if (!NostrController.instance) { NostrController.instance = new NostrController() @@ -206,60 +36,11 @@ export class NostrController extends EventEmitter { */ nip44Encrypt = async (receiver: string, content: string) => { // Retrieve the current login method from the application's redux state. - const loginMethod = (store.getState().auth as AuthState).loginMethod + const loginMethod = store.getState().auth.loginMethod + const context = new LoginMethodContext(loginMethod) // Handle encryption when the login method is via an extension. - if (loginMethod === LoginMethods.extension) { - const nostr = this.getNostrObject() - - // Check if the nostr object supports NIP-44 encryption. - if (!nostr.nip44) { - throw new Error( - `Your nostr extension does not support nip44 encryption & decryption` - ) - } - - // Encrypt the content using NIP-44 provided by the nostr extension. - const encrypted = await nostr.nip44.encrypt(receiver, content) - return encrypted as string - } - - // Handle encryption when the login method is via a private key. - if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - // Check if the private and public key pair is available. - if (!keys) { - throw new Error( - `Login method is ${LoginMethods.privateKey} but private & public key pair is not found.` - ) - } - - // Decode the private key. - const { private: nsec } = keys - const privateKey = nip19.decode(nsec).data as Uint8Array - - // Generate the conversation key using NIP-44 utilities. - const nip44ConversationKey = nip44.v2.utils.getConversationKey( - privateKey, - receiver - ) - - // Encrypt the content using the generated conversation key. - const encrypted = nip44.v2.encrypt(content, nip44ConversationKey) - - return encrypted - } - - // Throw an error if the login method is nsecBunker (not supported). - if (loginMethod === LoginMethods.nsecBunker) { - throw new Error( - `nip44 encryption is not yet supported for login method '${LoginMethods.nsecBunker}'` - ) - } - - // Throw an error if the login method is undefined or unsupported. - throw new Error('Login method is undefined') + return await context.nip44Encrypt(receiver, content) } /** @@ -272,180 +53,33 @@ export class NostrController extends EventEmitter { */ nip44Decrypt = async (sender: string, content: string) => { // Retrieve the current login method from the application's redux state. - const loginMethod = (store.getState().auth as AuthState).loginMethod + const loginMethod = store.getState().auth.loginMethod + const context = new LoginMethodContext(loginMethod) - // Handle decryption when the login method is via an extension. - if (loginMethod === LoginMethods.extension) { - const nostr = this.getNostrObject() - - // Check if the nostr object supports NIP-44 decryption. - if (!nostr.nip44) { - throw new Error( - `Your nostr extension does not support nip44 encryption & decryption` - ) - } - - // Decrypt the content using NIP-44 provided by the nostr extension. - const decrypted = await nostr.nip44.decrypt(sender, content) - return decrypted as string - } - - // Handle decryption when the login method is via a private key. - if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - // Check if the private and public key pair is available. - if (!keys) { - throw new Error( - `Login method is ${LoginMethods.privateKey} but private & public key pair is not found.` - ) - } - - // Decode the private key. - const { private: nsec } = keys - const privateKey = nip19.decode(nsec).data as Uint8Array - - // Generate the conversation key using NIP-44 utilities. - const nip44ConversationKey = nip44.v2.utils.getConversationKey( - privateKey, - sender - ) - - // Decrypt the content using the generated conversation key. - const decrypted = nip44.v2.decrypt(content, nip44ConversationKey) - - return decrypted - } - - // Throw an error if the login method is nsecBunker (not supported). - if (loginMethod === LoginMethods.nsecBunker) { - throw new Error( - `nip44 decryption is not yet supported for login method '${LoginMethods.nsecBunker}'` - ) - } - - // Throw an error if the login method is undefined or unsupported. - throw new Error('Login method is undefined') + // Handle decryption + return await context.nip44Decrypt(sender, content) } /** * Signs an event with private key (if it is present in local storage) or - * with browser extension (if it is present) or - * with nSecBunker instance. + * with browser extension (if it is present) * @param event - unsigned nostr event. * @returns - a promised that is resolved with signed nostr event. */ signEvent = async ( event: UnsignedEvent | EventTemplate ): Promise => { - const loginMethod = (store.getState().auth as AuthState).loginMethod + const loginMethod = store.getState().auth.loginMethod + const context = new LoginMethodContext(loginMethod) - if (!loginMethod) { - return Promise.reject('No login method found in the browser storage') - } - - if (loginMethod === LoginMethods.nsecBunker) { - // Check if nsecBunker is available - if (!this.bunkerNDK) { - return Promise.reject( - `Login method is ${loginMethod} but bunkerNDK is not created` - ) - } - - if (!this.remoteSigner) { - return Promise.reject( - `Login method is ${loginMethod} but bunkerNDK is not created` - ) - } - - const signedEvent = await this.signWithNsecBunker( - '', - event as NostrEvent - ).catch((err) => { - throw err - }) - - return Promise.resolve(signedEvent as SignedEvent) - } else if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - if (!keys) { - return Promise.reject( - `Login method is ${loginMethod}, but keys are not found` - ) - } - - const { private: nsec } = keys - const privateKey = nip19.decode(nsec).data as Uint8Array - - const signedEvent = finalizeEvent(event, privateKey) - - verifySignedEvent(signedEvent) - - return Promise.resolve(signedEvent) - } else if (loginMethod === LoginMethods.extension) { - const nostr = this.getNostrObject() - - return (await nostr - .signEvent(event as NostrEvent) - .catch((err: unknown) => { - console.log('Error while signing event: ', err) - - throw err - })) as Event - } else { - return Promise.reject( - `We could not sign the event, none of the signing methods are available` - ) - } + return await context.signEvent(event) } nip04Encrypt = async (receiver: string, content: string): Promise => { - const loginMethod = (store.getState().auth as AuthState).loginMethod + const loginMethod = store.getState().auth.loginMethod + const context = new LoginMethodContext(loginMethod) - if (loginMethod === LoginMethods.extension) { - const nostr = this.getNostrObject() - - if (!nostr.nip04) { - throw new Error( - `Your nostr extension does not support nip04 encryption & decryption` - ) - } - - const encrypted = await nostr.nip04.encrypt(receiver, content) - return encrypted - } - - if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - if (!keys) { - throw new Error( - `Login method is ${LoginMethods.privateKey} but private & public key pair is not found.` - ) - } - - const { private: nsec } = keys - const privateKey = nip19.decode(nsec).data as Uint8Array - - const encrypted = await nip04.encrypt(privateKey, receiver, content) - return encrypted - } - - if (loginMethod === LoginMethods.nsecBunker) { - const user = new NDKUser({ pubkey: receiver }) - - this.remoteSigner?.on('authUrl', (authUrl) => { - this.emit('nsecbunker-auth', authUrl) - }) - - if (!this.remoteSigner) throw new Error('Remote signer is undefined.') - const encrypted = await this.remoteSigner.encrypt(user, content) - - return encrypted - } - - throw new Error('Login method is undefined') + return await context.nip04Encrypt(receiver, content) } /** @@ -456,51 +90,10 @@ export class NostrController extends EventEmitter { * @returns A promise that resolves to the decrypted content. */ nip04Decrypt = async (sender: string, content: string): Promise => { - const loginMethod = (store.getState().auth as AuthState).loginMethod + const loginMethod = store.getState().auth.loginMethod + const context = new LoginMethodContext(loginMethod) - if (loginMethod === LoginMethods.extension) { - const nostr = this.getNostrObject() - - if (!nostr.nip04) { - throw new Error( - `Your nostr extension does not support nip04 encryption & decryption` - ) - } - - const decrypted = await nostr.nip04.decrypt(sender, content) - return decrypted - } - - if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - if (!keys) { - throw new Error( - `Login method is ${LoginMethods.privateKey} but private & public key pair is not found.` - ) - } - - const { private: nsec } = keys - const privateKey = nip19.decode(nsec).data as Uint8Array - - const decrypted = await nip04.decrypt(privateKey, sender, content) - return decrypted - } - - if (loginMethod === LoginMethods.nsecBunker) { - const user = new NDKUser({ pubkey: sender }) - - this.remoteSigner?.on('authUrl', (authUrl) => { - this.emit('nsecbunker-auth', authUrl) - }) - - if (!this.remoteSigner) throw new Error('Remote signer is undefined.') - const decrypted = await this.remoteSigner.decrypt(user, content) - - return decrypted - } - - throw new Error('Login method is undefined') + return await context.nip04Decrypt(sender, content) } /** @@ -523,12 +116,4 @@ export class NostrController extends EventEmitter { return Promise.resolve(pubKey) } - - /** - * Generates NDK Private Signer - * @returns nSecBunker delegated key - */ - generateDelegatedKey = (): string => { - return NDKPrivateKeySigner.generate().privateKey! - } } diff --git a/src/hooks/useLogout.tsx b/src/hooks/useLogout.tsx new file mode 100644 index 0000000..7c56bfe --- /dev/null +++ b/src/hooks/useLogout.tsx @@ -0,0 +1,26 @@ +import { logout as nostrLogout } from 'nostr-login' +import { clear } from '../utils/localStorage' +import { userLogOutAction } from '../store/actions' +import { LoginMethod } from '../store/auth/types' +import { useAppDispatch, useAppSelector } from './store' +import { useCallback } from 'react' + +export const useLogout = () => { + const loginMethod = useAppSelector((state) => state.auth?.loginMethod) + const dispatch = useAppDispatch() + + const logout = useCallback(() => { + // Log out of the nostr-login + if (loginMethod === LoginMethod.nostrLogin) { + nostrLogout() + } + + // Reset redux state with the logout + dispatch(userLogOutAction()) + + // Clear the local storage states + clear() + }, [dispatch, loginMethod]) + + return logout +} diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index 8106aba..8f79dc6 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -19,7 +19,6 @@ import { toast } from 'react-toastify' import { verifyEvent } from 'nostr-tools' import { Event } from 'nostr-tools' import store from '../store/store' -import { AuthState } from '../store/auth/types' import { NostrController } from '../controllers' import { MetaParseError } from '../types/errors/MetaParseError' @@ -146,7 +145,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { if (meta.keys) { const { sender, keys } = meta.keys // Retrieve the user's public key from the state - const usersPubkey = (store.getState().auth as AuthState).usersPubkey! + const usersPubkey = store.getState().auth.usersPubkey! const usersNpub = hexToNpub(usersPubkey) // Check if the user's public key is in the keys object diff --git a/src/index.css b/src/index.css index 7ee0eea..442454e 100644 --- a/src/index.css +++ b/src/index.css @@ -27,7 +27,7 @@ body { position: fixed; top: 80px; right: 20px; - z-index: 100; + z-index: 40; } #root { diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index 3a1f10e..19ac4d9 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -1,87 +1,160 @@ -import { Event, kinds } from 'nostr-tools' -import { useEffect, useRef, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { Outlet } from 'react-router-dom' +import { Event, getPublicKey, kinds, nip19 } from 'nostr-tools' +import { useCallback, useEffect, useRef, useState } from 'react' +import { Outlet, useNavigate, useSearchParams } from 'react-router-dom' import { AppBar } from '../components/AppBar/AppBar' import { LoadingSpinner } from '../components/LoadingSpinner' -import { MetadataController, NostrController } from '../controllers' +import { + AuthController, + MetadataController, + NostrController +} from '../controllers' import { restoreState, - setAuthState, setMetadataEvent, + updateKeyPair, + updateLoginMethod, + updateNostrLoginAuthMethod, updateUserAppData } from '../store/actions' -import { LoginMethods } from '../store/auth/types' -import { State } from '../store/rootReducer' -import { Dispatch } from '../store/store' import { setUserRobotImage } from '../store/userRobotImage/action' import { - clearAuthToken, - clearState, getRoboHashPicture, getUsersAppData, loadState, - saveNsecBunkerDelegatedKey, subscribeForSigits } from '../utils' -import { useAppSelector } from '../hooks' +import { useAppDispatch, useAppSelector } from '../hooks' import styles from './style.module.scss' +import { useLogout } from '../hooks/useLogout' +import { LoginMethod } from '../store/auth/types' +import { NostrLoginAuthOptions } from 'nostr-login/dist/types' +import { init as initNostrLogin } from 'nostr-login' export const MainLayout = () => { - const dispatch: Dispatch = useDispatch() + const [searchParams, setSearchParams] = useSearchParams() + const navigate = useNavigate() + const dispatch = useAppDispatch() + const logout = useLogout() const [isLoading, setIsLoading] = useState(true) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`) - const authState = useSelector((state: State) => state.auth) + const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn) + const authState = useAppSelector((state) => state.auth) const usersAppData = useAppSelector((state) => state.userAppData) // Ref to track if `subscribeForSigits` has been called const hasSubscribed = useRef(false) + const navigateAfterLogin = (path: string) => { + const callbackPath = searchParams.get('callbackPath') + + if (callbackPath) { + // base64 decoded path + const path = atob(callbackPath) + navigate(path) + return + } + + navigate(path) + } + + const login = useCallback(async () => { + const nostrController = NostrController.getInstance() + const authController = new AuthController() + const pubkey = await nostrController.capturePublicKey() + + dispatch(updateLoginMethod(LoginMethod.nostrLogin)) + + const redirectPath = + await authController.authAndGetMetadataAndRelaysMap(pubkey) + + if (redirectPath) { + navigateAfterLogin(redirectPath) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]) + + useEffect(() => { + // Developer login with ?nsec= (not recommended) + const nsec = searchParams.get('nsec') + if (!nsec) return + + // Clear nsec from the url immediately + searchParams.delete('nsec') + setSearchParams(searchParams) + + if (!authState?.loggedIn) { + if (!nsec.startsWith('nsec')) { + console.error('Invalid format, use private key (nsec)') + return + } + + try { + const privateKey = nip19.decode(nsec).data as Uint8Array + if (!privateKey) { + console.error('Failed to convert the private key.') + return + } + + const publickey = getPublicKey(privateKey) + + dispatch( + updateKeyPair({ + private: nsec, + public: publickey + }) + ) + dispatch(updateLoginMethod(LoginMethod.privateKey)) + + const authController = new AuthController() + authController + .authAndGetMetadataAndRelaysMap(publickey) + .catch((err) => { + console.error('Error occurred in authentication: ' + err) + return null + }) + } catch (err) { + console.error(`Error decoding the nsec. ${err}`) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, searchParams]) + + useEffect(() => { + const handleNostrAuth = (_: string, opts: NostrLoginAuthOptions) => { + if (opts.type === 'login' || opts.type === 'signup') { + dispatch(updateNostrLoginAuthMethod(opts.method)) + login() + } else if (opts.type === 'logout') { + // Clear `subscribeForSigits` as called after the logout + hasSubscribed.current = false + } + } + + // Initialize the nostr-login + initNostrLogin({ + methods: ['connect', 'extension', 'local'], + noBanner: true, + onAuth: handleNostrAuth + }).catch((error) => { + console.error('Failed to initialize Nostr-Login', error) + }) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]) + useEffect(() => { const metadataController = MetadataController.getInstance() - const logout = () => { - dispatch( - setAuthState({ - keyPair: undefined, - loggedIn: false, - usersPubkey: undefined, - loginMethod: undefined, - nsecBunkerPubkey: undefined - }) - ) - - dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent())) - - // clear authToken saved in local storage - clearAuthToken() - clearState() - - // update nsecBunker delegated key - const newDelegatedKey = - NostrController.getInstance().generateDelegatedKey() - saveNsecBunkerDelegatedKey(newDelegatedKey) - } - const restoredState = loadState() if (restoredState) { dispatch(restoreState(restoredState)) - const { loggedIn, loginMethod, usersPubkey, nsecBunkerRelays } = - restoredState.auth + const { loggedIn, loginMethod, usersPubkey } = restoredState.auth if (loggedIn) { if (!loginMethod || !usersPubkey) return logout() - if (loginMethod === LoginMethods.nsecBunker) { - if (!nsecBunkerRelays) return logout() - - const nostrController = NostrController.getInstance() - nostrController.nsecBunkerInit(nsecBunkerRelays).then(() => { - nostrController.createNsecBunkerSigner(usersPubkey) - }) - } - + // Update user profile metadata, old state might be outdated const handleMetadataEvent = (event: Event) => { dispatch(setMetadataEvent(event)) } @@ -101,10 +174,14 @@ export const MainLayout = () => { } else { setIsLoading(false) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [dispatch]) + /** + * Subscribe for the sigits + */ useEffect(() => { - if (authState.loggedIn && usersAppData) { + if (authState && isLoggedIn && usersAppData) { const pubkey = authState.usersPubkey || authState.keyPair?.public if (pubkey && !hasSubscribed.current) { @@ -116,7 +193,7 @@ export const MainLayout = () => { hasSubscribed.current = true } } - }, [authState, usersAppData]) + }, [authState, isLoggedIn, usersAppData]) /** * When authState change user logged in / or app reloaded @@ -124,7 +201,7 @@ export const MainLayout = () => { * so that avatar will be consistent across the app when kind 0 is empty */ useEffect(() => { - if (authState && authState.loggedIn) { + if (authState && isLoggedIn) { const pubkey = authState.usersPubkey || authState.keyPair?.public if (pubkey) { @@ -141,7 +218,8 @@ export const MainLayout = () => { }) .finally(() => setIsLoading(false)) } - }, [authState, dispatch]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, isLoggedIn]) if (isLoading) return diff --git a/src/layouts/modal/index.tsx b/src/layouts/modal/index.tsx index 7b29379..663e890 100644 --- a/src/layouts/modal/index.tsx +++ b/src/layouts/modal/index.tsx @@ -33,7 +33,7 @@ export const Modal = () => { { to: appPublicRoutes.register, title: 'Register', label: 'Register' }, { to: appPublicRoutes.nostr, - title: 'Login', + title: 'Nostr Login', sx: { padding: '10px' }, label: nostr logo } diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 76e485b..866bdb5 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -8,14 +8,13 @@ import { useEffect, useRef, useState } from 'react' import { DndProvider, useDrag, useDrop } from 'react-dnd' import { MultiBackend } from 'react-dnd-multi-backend' import { HTML5toTouch } from 'rdndmb-html5-to-touch' -import { useSelector } from 'react-redux' +import { useAppSelector } from '../../hooks/store' import { useLocation, useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { UserAvatar } from '../../components/UserAvatar' import { MetadataController, NostrController } from '../../controllers' import { appPrivateRoutes } from '../../routes' -import { State } from '../../store/rootReducer' import { CreateSignatureEventContent, Meta, @@ -76,8 +75,6 @@ export const CreatePage = () => { const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') - const [authUrl, setAuthUrl] = useState() - const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`) const [selectedFiles, setSelectedFiles] = useState([]) @@ -102,7 +99,7 @@ export const CreatePage = () => { const signers = users.filter((u) => u.role === UserRole.signer) const viewers = users.filter((u) => u.role === UserRole.viewer) - const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) + const usersPubkey = useAppSelector((state) => state.auth.usersPubkey) const nostrController = NostrController.getInstance() @@ -185,10 +182,6 @@ export const CreatePage = () => { } }) }, [metadata, users]) - // Set up event listener for authentication event - nostrController.on('nsecbunker-auth', (url) => { - setAuthUrl(url) - }) useEffect(() => { if (uploadedFiles) { @@ -255,17 +248,22 @@ export const CreatePage = () => { const input = userInput.toLowerCase() if (input.startsWith('npub')) { - const pubkey = npubToHex(input) - if (pubkey) { - addUser(pubkey) - setUserInput('') - } else { - setError('Provided npub is not valid. Please enter correct npub.') - } - return + return handleAddNpubUser(input) } if (input.includes('@')) { + return await handleAddNip05User(input) + } + + // If the user enters the domain (w/o @) assume it's the "root" and append _@ + // https://github.com/nostr-protocol/nips/blob/master/05.md#showing-just-the-domain-as-an-identifier + if (input.includes('.')) { + return await handleAddNip05User(`_@${input}`) + } + + setError('Invalid input! Make sure to provide correct npub or nip05.') + + async function handleAddNip05User(input: string) { setIsLoading(true) setLoadingSpinnerDesc('Querying for nip05') const nip05Profile = await queryNip05(input) @@ -288,7 +286,16 @@ export const CreatePage = () => { return } - setError('Invalid input! Make sure to provide correct npub or nip05.') + function handleAddNpubUser(input: string) { + const pubkey = npubToHex(input) + if (pubkey) { + addUser(pubkey) + setUserInput('') + } else { + setError('Provided npub is not valid. Please enter correct npub.') + } + return + } } const handleUserRoleChange = (role: UserRole, pubkey: string) => { @@ -778,17 +785,6 @@ export const CreatePage = () => { } } - if (authUrl) { - return ( -