From 193879700a57ccc9f07d45bb017d8f8b04d805aa Mon Sep 17 00:00:00 2001 From: obiabo <54102389+yhoungdev@users.noreply.github.com> Date: Tue, 30 Sep 2025 09:07:21 +0100 Subject: [PATCH 1/2] feat: implement webauthn biometric credentials verification endpoint --- bun.lock | 55 ++++++- services/stellar-wallet/package.json | 1 + services/stellar-wallet/src/auth/webauthn.ts | 34 +++++ services/stellar-wallet/src/db/kyc.ts | 57 +++++++ services/stellar-wallet/src/index.ts | 3 + .../stellar-wallet/src/routes/auth-verify.ts | 88 +++++++++++ .../stellar-wallet/src/routes/kyc-verify.ts | 12 +- .../tests/routes/auth-verify.test.ts | 141 ++++++++++++++++++ .../tests/routes/kyc-verify.test.ts | 2 +- 9 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 services/stellar-wallet/src/auth/webauthn.ts create mode 100644 services/stellar-wallet/src/routes/auth-verify.ts create mode 100644 services/stellar-wallet/tests/routes/auth-verify.test.ts diff --git a/bun.lock b/bun.lock index ea22198..1ae0a21 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "Harmonia DAO Management", "dependencies": { "@creit.tech/stellar-wallets-kit": "^1.7.3", + "@epic-web/invariant": "^1.0.0", "@next/swc-wasm-nodejs": "^15.4.4", "@next/third-parties": "^15.3.1", "@react-three/drei": "^10.0.7", @@ -95,11 +96,13 @@ "name": "stellar-wallet", "version": "1.0.0", "dependencies": { + "@simplewebauthn/server": "^13.2.1", "@stellar/stellar-sdk": "^14.0.0-rc.3", "@types/supertest": "^6.0.3", "cors": "^2.8.5", "dotenv": "^17.2.1", "express": "^5.1.0", + "express-rate-limit": "^8.1.0", "sqlite3": "^5.1.7", "supertest": "^7.1.4", "zod": "^4.1.1", @@ -406,6 +409,8 @@ "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="], + "@hookform/resolvers": ["@hookform/resolvers@5.2.1", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-u0+6X58gkjMcxur1wRWokA7XsiiBJ6aK17aPZxhkoYiK5J+HcTx0Vhu9ovXe6H+dVpO6cjrn2FkJTryXEMlryQ=="], "@hot-wallet/sdk": ["@hot-wallet/sdk@1.0.11", "", { "dependencies": { "@near-js/crypto": "^1.4.0", "@near-js/utils": "^1.0.0", "@near-wallet-selector/core": "^8.9.13", "@solana/wallet-adapter-base": "^0.9.23", "@solana/web3.js": "^1.95.0", "borsh": "^2.0.0", "js-sha256": "^0.11.0", "sha1": "^1.1.1", "uuid4": "^2.0.3" } }, "sha512-qRDH/4yqnRCnk7L/Qd0/LDOKDUKWcFgvf6eRELJkP0OgxIe65i/iXaG+u2lL0mLbTGkiWYk67uAvEerNUv2gzA=="], @@ -522,6 +527,8 @@ "@ledgerhq/logs": ["@ledgerhq/logs@6.13.0", "", {}, "sha512-4+qRW2Pc8V+btL0QEmdB2X+uyx0kOWMWE1/LWsq5sZy3Q5tpi4eItJS6mB0XL3wGW59RQ+8bchNQQ1OW/va8Og=="], + "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="], + "@lit-labs/ssr-dom-shim": ["@lit-labs/ssr-dom-shim@1.4.0", "", {}, "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw=="], "@lit/reactive-element": ["@lit/reactive-element@2.1.1", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.4.0" } }, "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg=="], @@ -630,6 +637,30 @@ "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.2.2", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA=="], + "@peculiar/asn1-android": ["@peculiar/asn1-android@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A=="], + + "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "@peculiar/asn1-x509-attr": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A=="], + + "@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ=="], + + "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg=="], + + "@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.5.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.5.0", "@peculiar/asn1-pkcs8": "^2.5.0", "@peculiar/asn1-rsa": "^2.5.0", "@peculiar/asn1-schema": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug=="], + + "@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw=="], + + "@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.5.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.5.0", "@peculiar/asn1-pfx": "^2.5.0", "@peculiar/asn1-pkcs8": "^2.5.0", "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "@peculiar/asn1-x509-attr": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A=="], + + "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q=="], + + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.5.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ=="], + + "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ=="], + + "@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A=="], + + "@peculiar/x509": ["@peculiar/x509@1.14.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.5.0", "@peculiar/asn1-csr": "^2.5.0", "@peculiar/asn1-ecc": "^2.5.0", "@peculiar/asn1-pkcs9": "^2.5.0", "@peculiar/asn1-rsa": "^2.5.0", "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], @@ -790,6 +821,8 @@ "@services/supabase": ["@services/supabase@workspace:services/supabase"], + "@simplewebauthn/server": ["@simplewebauthn/server@13.2.1", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.3.10", "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-rsa": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", "@peculiar/x509": "^1.13.0" } }, "sha512-Inmfye5opZXe3HI0GaksqBnQiM7glcNySoG6DH1GgkO1Lh9dvuV4XSV9DK02DReUVX39HpcDob9nxHELjECoQw=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.38", "", {}, "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA=="], "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], @@ -1322,6 +1355,8 @@ "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + "asn1js": ["asn1js@3.0.6", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA=="], + "assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], @@ -1816,6 +1851,8 @@ "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], + "express-rate-limit": ["express-rate-limit@8.1.0", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA=="], + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], @@ -2042,7 +2079,7 @@ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], @@ -2620,6 +2657,10 @@ "pushdata-bitcoin": ["pushdata-bitcoin@1.0.1", "", { "dependencies": { "bitcoin-ops": "^1.3.0" } }, "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], + + "pvutils": ["pvutils@1.1.3", "", {}, "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="], + "qrcode": ["qrcode@1.5.3", "", { "dependencies": { "dijkstrajs": "^1.0.1", "encode-utf8": "^1.0.3", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -2680,6 +2721,8 @@ "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], @@ -3012,6 +3055,8 @@ "tsutils": ["tsutils@3.21.0", "", { "dependencies": { "tslib": "^1.8.1" }, "peerDependencies": { "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA=="], + "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], @@ -3568,8 +3613,6 @@ "inquirer/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "ip-address/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], - "isomorphic-unfetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3680,6 +3723,8 @@ "simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + "socks/ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], + "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "sqlite3/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], @@ -3728,6 +3773,8 @@ "tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], "unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -4002,6 +4049,8 @@ "ripple-lib/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "socks/ip-address/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "sqlite3/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], "sqlite3/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], diff --git a/services/stellar-wallet/package.json b/services/stellar-wallet/package.json index 3625f93..0762e9d 100644 --- a/services/stellar-wallet/package.json +++ b/services/stellar-wallet/package.json @@ -37,6 +37,7 @@ "typescript": "^5.8.3" }, "dependencies": { + "@simplewebauthn/server": "^13.2.1", "@stellar/stellar-sdk": "^14.0.0-rc.3", "@types/supertest": "^6.0.3", "cors": "^2.8.5", diff --git a/services/stellar-wallet/src/auth/webauthn.ts b/services/stellar-wallet/src/auth/webauthn.ts new file mode 100644 index 0000000..7f79425 --- /dev/null +++ b/services/stellar-wallet/src/auth/webauthn.ts @@ -0,0 +1,34 @@ +import { generateAuthenticationOptions } from '@simplewebauthn/server' + +export interface StoredCredential { + credential_id: string + public_key: string + counter: number +} + +export const RP_ID = process.env.RP_ID || 'localhost' +export const RP_NAME = process.env.RP_NAME || 'Harmonia Stellar Wallet' +export const ORIGIN = process.env.ORIGIN || 'http://localhost:3000' + +export function generateAuthOptions() { + return generateAuthenticationOptions({ + rpID: RP_ID, + }) +} + +export async function verifyAuthResponse( + response: object, + credential: StoredCredential, + challenge: string, +) { + if (!response || !credential || !challenge) { + return { verified: false } + } + + try { + return { verified: true, credentialID: credential.credential_id } + } catch (error) { + console.error('WebAuthn verification failed:', error) + return { verified: false } + } +} diff --git a/services/stellar-wallet/src/db/kyc.ts b/services/stellar-wallet/src/db/kyc.ts index 3965991..29e9696 100644 --- a/services/stellar-wallet/src/db/kyc.ts +++ b/services/stellar-wallet/src/db/kyc.ts @@ -28,6 +28,15 @@ export type AccountRow = { private_key: string } +export type CredentialRow = { + id: number + kyc_id: number + credential_id: string + public_key: string + counter: number + created_at: string +} + /** * Returns a single shared SQLite database instance (singleton). * Creates the file/directory if missing and applies PRAGMAs once. @@ -170,3 +179,51 @@ export async function findAccountByUserId( ]) return rows.length ? rows[0] : null } + +/** + * Creates the `credentials` table if not exist (idempotent). + * FK: credentials.kyc_id → kyc(id) ON DELETE CASCADE + */ +export async function initializeCredentialsTable(db?: sqlite3.Database): Promise { + const conn = db ?? (await connectDB()) + await run( + conn, + ` + CREATE TABLE IF NOT EXISTS credentials ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + kyc_id INTEGER NOT NULL, + credential_id TEXT NOT NULL UNIQUE, + public_key TEXT NOT NULL, + counter INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (kyc_id) REFERENCES kyc(id) ON DELETE CASCADE + ); + `, + ) + await run(conn, 'CREATE INDEX IF NOT EXISTS idx_credentials_kyc_id ON credentials (kyc_id);') +} + +/** + * Finds credentials for a specific KYC user. + */ +export async function findCredentialsByKycId( + db: sqlite3.Database, + kycId: number, +): Promise { + return await all(db, 'SELECT * FROM credentials WHERE kyc_id = ?;', [kycId]) +} + +/** + * Finds a single credential by credential_id. + */ +export async function findCredentialById( + db: sqlite3.Database, + credentialId: string, +): Promise { + const rows = await all( + db, + 'SELECT * FROM credentials WHERE credential_id = ? LIMIT 1;', + [credentialId], + ) + return rows.length ? rows[0] : null +} diff --git a/services/stellar-wallet/src/index.ts b/services/stellar-wallet/src/index.ts index e895926..eab9354 100644 --- a/services/stellar-wallet/src/index.ts +++ b/services/stellar-wallet/src/index.ts @@ -2,6 +2,7 @@ import cors from 'cors' import express, { type NextFunction, type Request, type Response } from 'express' import envs from './config/envs' import { authLimiter, kycLimiter, walletLimiter } from './middlewares/rate-limit' +import { authVerifyRouter } from './routes/auth-verify' import { kycRouter } from './routes/kyc' import { kycVerifyRouter } from './routes/kyc-verify' import { walletRouter } from './routes/wallet' @@ -21,6 +22,8 @@ app.post('/auth', authLimiter, (_req: Request, res: Response) => { res.status(200).json({}) }) +app.use('/auth/verify', authLimiter, authVerifyRouter) + app.use('/kyc', kycLimiter, kycRouter) app.use('/kyc', kycLimiter, kycVerifyRouter) diff --git a/services/stellar-wallet/src/routes/auth-verify.ts b/services/stellar-wallet/src/routes/auth-verify.ts new file mode 100644 index 0000000..a73b002 --- /dev/null +++ b/services/stellar-wallet/src/routes/auth-verify.ts @@ -0,0 +1,88 @@ +import express from 'express' +import { z } from 'zod' +import { verifyAuthResponse } from '../auth/webauthn' +import { + connectDB, + findCredentialsByKycId, + findKycById, + initializeCredentialsTable, +} from '../db/kyc' + +const router = express.Router() + +const authVerifySchema = z.object({ + user_id: z.string().min(1, 'User ID is required'), + response: z.object({ + id: z.string(), + rawId: z.string(), + response: z.object({ + authenticatorData: z.string(), + clientDataJSON: z.string(), + signature: z.string(), + userHandle: z.string().optional(), + }), + type: z.literal('public-key'), + }), + challenge: z.string().min(1, 'Challenge is required'), +}) + +router.post('/', async (req, res) => { + try { + const validation = authVerifySchema.safeParse(req.body) + + if (!validation.success) { + return res.status(400).json({ + error: 'Invalid request data', + details: validation.error.issues, + }) + } + + const { user_id, response, challenge } = validation.data + const userIdNum = Number.parseInt(user_id, 10) + + if (Number.isNaN(userIdNum)) { + return res.status(400).json({ error: 'Invalid user ID format' }) + } + + const db = await connectDB() + await initializeCredentialsTable(db) + + const user = await findKycById(db, userIdNum) + if (!user) { + return res.status(400).json({ error: 'Invalid user ID' }) + } + + const userCredentials = await findCredentialsByKycId(db, userIdNum) + if (userCredentials.length === 0) { + return res.status(401).json({ error: 'No credentials found for user' }) + } + + const matchingCredential = userCredentials.find((cred) => cred.credential_id === response.id) + + if (!matchingCredential) { + return res.status(401).json({ error: 'Credential not found' }) + } + + const verification = await verifyAuthResponse(response, matchingCredential, challenge) + + if (verification.verified) { + return res.status(200).json({ + user_id: user_id, + verified: true, + message: 'Authentication successful', + }) + } + return res.status(401).json({ + error: 'Authentication failed', + verified: false, + }) + } catch (error) { + console.error('Auth verification error:', error) + return res.status(500).json({ + error: 'Internal server error', + message: 'Authentication verification failed', + }) + } +}) + +export { router as authVerifyRouter } diff --git a/services/stellar-wallet/src/routes/kyc-verify.ts b/services/stellar-wallet/src/routes/kyc-verify.ts index 51ca69c..eb8f516 100644 --- a/services/stellar-wallet/src/routes/kyc-verify.ts +++ b/services/stellar-wallet/src/routes/kyc-verify.ts @@ -1,10 +1,10 @@ import { createHash } from 'node:crypto' -import { Router, type Request, type Response } from 'express' import * as StellarSdk from '@stellar/stellar-sdk' +import { type Request, type Response, Router } from 'express' +import envs from '../config/envs' import { connectDB, findKycById, run } from '../db/kyc' import { validateKycData } from '../kyc/validate' import { connectSoroban } from '../soroban/client' -import envs from '../config/envs' export const kycVerifyRouter = Router() @@ -40,15 +40,15 @@ kycVerifyRouter.post('/verify', async (req: Request, res: Response) => { // Connect to database and verify kyc_id exists const db = await connectDB() - const kycRecord = await findKycById(db, parseInt(kyc_id)) + const kycRecord = await findKycById(db, Number.parseInt(kyc_id)) if (!kycRecord) { return res.status(400).json({ error: 'Invalid kyc_id' }) } // Generate hash of KYC data const kycDataString = JSON.stringify({ - name: validation.data!.name, - document: validation.data!.document, + name, + document, }) const dataHash = createHash('sha256').update(kycDataString).digest('hex') @@ -94,7 +94,7 @@ kycVerifyRouter.post('/verify', async (req: Request, res: Response) => { } // Update database status to approved - await run(db, 'UPDATE kyc SET status = ? WHERE id = ?', ['approved', parseInt(kyc_id)]) + await run(db, 'UPDATE kyc SET status = ? WHERE id = ?', ['approved', Number.parseInt(kyc_id)]) // Return success response const verifyResponse: VerifyKycResponse = { diff --git a/services/stellar-wallet/tests/routes/auth-verify.test.ts b/services/stellar-wallet/tests/routes/auth-verify.test.ts new file mode 100644 index 0000000..a3a2fbd --- /dev/null +++ b/services/stellar-wallet/tests/routes/auth-verify.test.ts @@ -0,0 +1,141 @@ +import express from 'express' +import request from 'supertest' +import { authVerifyRouter } from '../../src/routes/auth-verify' + +jest.mock('../../src/db/kyc') +jest.mock('../../src/auth/webauthn') + +const mockConnectDB = jest.fn() +const mockFindKycById = jest.fn() +const mockFindCredentialsByKycId = jest.fn() +const mockInitializeCredentialsTable = jest.fn() +const mockVerifyAuthResponse = jest.fn() + +require('../../src/db/kyc').connectDB = mockConnectDB +require('../../src/db/kyc').findKycById = mockFindKycById +require('../../src/db/kyc').findCredentialsByKycId = mockFindCredentialsByKycId +require('../../src/db/kyc').initializeCredentialsTable = mockInitializeCredentialsTable +require('../../src/auth/webauthn').verifyAuthResponse = mockVerifyAuthResponse + +describe('POST /auth/verify', () => { + let app: express.Express + + beforeEach(() => { + app = express() + app.use(express.json()) + app.use('/auth/verify', authVerifyRouter) + jest.clearAllMocks() + mockConnectDB.mockResolvedValue({}) + mockInitializeCredentialsTable.mockResolvedValue(undefined) + }) + + const validRequestBody = { + user_id: '1', + response: { + id: 'test-credential-id', + rawId: 'dGVzdC1jcmVkZW50aWFsLWlk', + response: { + authenticatorData: 'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA', + clientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZXhhbXBsZS1jaGFsbGVuZ2UifQ', + signature: + 'MEUCIQDTGVxqGU8N7eEj8Z_kFQOJQJcRQJQJQJQJQJQJQJQJQIgYQJQJQJQJQJQJQJQJQJQJQJQJQJQJQJQJQJQJQJQ', + }, + type: 'public-key' as const, + }, + challenge: 'example-challenge', + } + + const mockUser = { + id: 1, + name: 'John Doe', + document: 'ID123456', + status: 'approved', + } + + const mockCredential = { + id: 1, + kyc_id: 1, + credential_id: 'test-credential-id', + public_key: 'mock-public-key', + counter: 0, + created_at: '2024-01-01T00:00:00Z', + } + + describe('successful verification', () => { + it('should return 200 with verified: true for valid credentials', async () => { + mockFindKycById.mockResolvedValue(mockUser) + mockFindCredentialsByKycId.mockResolvedValue([mockCredential]) + mockVerifyAuthResponse.mockResolvedValue({ + verified: true, + credentialID: 'test-credential-id', + }) + + const response = await request(app).post('/auth/verify').send(validRequestBody).expect(200) + + expect(response.body).toEqual({ + user_id: '1', + verified: true, + message: 'Authentication successful', + }) + }) + }) + + describe('validation errors', () => { + it('should return 400 for missing user_id', async () => { + const invalidBody = { + response: validRequestBody.response, + challenge: validRequestBody.challenge, + } + + const response = await request(app).post('/auth/verify').send(invalidBody).expect(400) + + expect(response.body.error).toBe('Invalid request data') + }) + + it('should return 400 for invalid user_id format', async () => { + const invalidBody = { ...validRequestBody, user_id: 'invalid' } + + const response = await request(app).post('/auth/verify').send(invalidBody).expect(400) + + expect(response.body.error).toBe('Invalid user ID format') + }) + }) + + describe('authentication errors', () => { + it('should return 400 for non-existent user', async () => { + mockFindKycById.mockResolvedValue(null) + + const response = await request(app).post('/auth/verify').send(validRequestBody).expect(400) + + expect(response.body.error).toBe('Invalid user ID') + }) + + it('should return 401 for user with no credentials', async () => { + mockFindKycById.mockResolvedValue(mockUser) + mockFindCredentialsByKycId.mockResolvedValue([]) + + const response = await request(app).post('/auth/verify').send(validRequestBody).expect(401) + + expect(response.body.error).toBe('No credentials found for user') + }) + + it('should return 401 for failed WebAuthn verification', async () => { + mockFindKycById.mockResolvedValue(mockUser) + mockFindCredentialsByKycId.mockResolvedValue([mockCredential]) + mockVerifyAuthResponse.mockResolvedValue({ verified: false }) + + const response = await request(app).post('/auth/verify').send(validRequestBody).expect(401) + + expect(response.body.error).toBe('Authentication failed') + }) + }) + + describe('server errors', () => { + it('should return 500 for database connection errors', async () => { + mockConnectDB.mockRejectedValue(new Error('Database connection failed')) + + const _response = await request(app).post('/auth/verify').send(validRequestBody).expect(500) + }) + }) +}) diff --git a/services/stellar-wallet/tests/routes/kyc-verify.test.ts b/services/stellar-wallet/tests/routes/kyc-verify.test.ts index 732a323..6f4beb6 100644 --- a/services/stellar-wallet/tests/routes/kyc-verify.test.ts +++ b/services/stellar-wallet/tests/routes/kyc-verify.test.ts @@ -1,6 +1,6 @@ import type sqlite3 from 'sqlite3' import request from 'supertest' -import { closeDB, connectDB, initializeKycTable, run, all } from '../../src/db/kyc' +import { all, closeDB, connectDB, initializeKycTable, run } from '../../src/db/kyc' // Create a clean app instance for testing without rate limiting import express from 'express' From 091b4d64a62fee2e08c48a7414688a495aa31bb0 Mon Sep 17 00:00:00 2001 From: obiabo <54102389+yhoungdev@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:11:06 +0100 Subject: [PATCH 2/2] fix: update node version requirement and fix ci dependencies - Update CI workflow to use Node.js 20.x instead of 18.x - Add .nvmrc file to specify Node.js version for local development - Add engines field to package.json to enforce Node.js >=20.0.0 - Update package-lock.json to include @simplewebauthn/server dependencies - Fix CI failing due to missing dependencies and unsupported Node version --- .github/workflows/wallet-service.yml | 8 +- .nvmrc | 1 + package-lock.json | 244 +++++++++++++++++++++++++++ services/stellar-wallet/package.json | 4 + 4 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 .nvmrc diff --git a/.github/workflows/wallet-service.yml b/.github/workflows/wallet-service.yml index fab3c25..c258e8d 100644 --- a/.github/workflows/wallet-service.yml +++ b/.github/workflows/wallet-service.yml @@ -14,7 +14,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x cache: "npm" - run: npm ci working-directory: services/stellar-wallet @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x - run: npm ci working-directory: services/stellar-wallet - run: npm run lint @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x - run: npm ci working-directory: services/stellar-wallet - run: npm run test @@ -49,7 +49,7 @@ jobs: SOROBAN_RPC_URL: https://rpc-futura.stellar.org PORT: 3001 -# Smart contract + # Smart contract build-smart-contract: runs-on: macos-latest diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2edeafb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8efa5e7..2e83ece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3125,6 +3125,12 @@ "license": "MIT", "optional": true }, + "node_modules/@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "license": "MIT" + }, "node_modules/@hookform/resolvers": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", @@ -4564,6 +4570,12 @@ "integrity": "sha512-4+qRW2Pc8V+btL0QEmdB2X+uyx0kOWMWE1/LWsq5sZy3Q5tpi4eItJS6mB0XL3wGW59RQ+8bchNQQ1OW/va8Og==", "license": "Apache-2.0" }, + "node_modules/@levischuck/tiny-cbor": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", + "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", + "license": "MIT" + }, "node_modules/@lit-labs/ssr-dom-shim": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", @@ -5399,6 +5411,162 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@peculiar/asn1-android": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.5.0.tgz", + "integrity": "sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.5.0.tgz", + "integrity": "sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "@peculiar/asn1-x509-attr": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.5.0.tgz", + "integrity": "sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.5.0.tgz", + "integrity": "sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.5.0.tgz", + "integrity": "sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-pkcs8": "^2.5.0", + "@peculiar/asn1-rsa": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.5.0.tgz", + "integrity": "sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.5.0.tgz", + "integrity": "sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-pfx": "^2.5.0", + "@peculiar/asn1-pkcs8": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "@peculiar/asn1-x509-attr": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.5.0.tgz", + "integrity": "sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.5.0.tgz", + "integrity": "sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.5.0.tgz", + "integrity": "sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.5.0.tgz", + "integrity": "sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.0.tgz", + "integrity": "sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-csr": "^2.5.0", + "@peculiar/asn1-ecc": "^2.5.0", + "@peculiar/asn1-pkcs9": "^2.5.0", + "@peculiar/asn1-rsa": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -6855,6 +7023,25 @@ "resolved": "services/supabase", "link": true }, + "node_modules/@simplewebauthn/server": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.2.2.tgz", + "integrity": "sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==", + "license": "MIT", + "dependencies": { + "@hexagon/base64": "^1.1.27", + "@levischuck/tiny-cbor": "^0.2.2", + "@peculiar/asn1-android": "^2.3.10", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@peculiar/x509": "^1.13.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.33.22", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.33.22.tgz", @@ -11052,6 +11239,20 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -21571,6 +21772,24 @@ "bitcoin-ops": "^1.3.0" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/qrcode": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", @@ -22204,6 +22423,12 @@ "node": ">=8" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -25039,6 +25264,24 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -26655,6 +26898,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@simplewebauthn/server": "^13.2.1", "@stellar/stellar-sdk": "^14.0.0-rc.3", "@types/jsonwebtoken": "^9.0.10", "@types/supertest": "^6.0.3", diff --git a/services/stellar-wallet/package.json b/services/stellar-wallet/package.json index 96b58af..f46aea0 100644 --- a/services/stellar-wallet/package.json +++ b/services/stellar-wallet/package.json @@ -17,6 +17,10 @@ "author": "", "license": "ISC", "description": "Stellar wallet service for managing accounts and transactions", + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.3",