summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.cjs8
-rw-r--r--.nvmrc1
-rw-r--r--README.md4
-rw-r--r--package-lock.json446
-rw-r--r--package.json18
-rw-r--r--src/client/App.tsx103
-rw-r--r--src/client/main.tsx2
-rw-r--r--src/client/utils.ts19
-rw-r--r--src/server/main.ts316
-rw-r--r--src/server/utils.ts10
10 files changed, 712 insertions, 215 deletions
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index e7f58a7..2a228e9 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -10,13 +10,9 @@ module.exports = {
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'node_modules'],
parser: '@typescript-eslint/parser',
- plugins: ['react-refresh', '@typescript-eslint', 'prettier'],
+ plugins: ['@typescript-eslint', 'prettier'],
rules: {
'prettier/prettier': 'warn', // Show Prettier issues as warnings
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
'@typescript-eslint/no-unused-vars': [
'warn',
{
@@ -32,4 +28,4 @@ module.exports = {
version: 'detect' // Automatically detect the React version
}
}
-}; \ No newline at end of file
+};
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..5bd6811
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20.19.0
diff --git a/README.md b/README.md
index de78194..7e5778c 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ digiid-ts-demo/
### Prerequisites
-- Node.js (v16 or higher)
+- Node.js (v20.19.0 or higher)
- npm or yarn
- A DigiByte wallet that supports Digi-ID (e.g., DigiByte Go)
@@ -173,4 +173,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Acknowledgments
- [DigiByte](https://www.digibyte.org/) - The underlying blockchain technology
-- [digiid-ts](https://github.com/pawelzelawski/digiid-ts) - The TypeScript library for Digi-ID authentication \ No newline at end of file
+- [digiid-ts](https://github.com/pawelzelawski/digiid-ts) - The TypeScript library for Digi-ID authentication
diff --git a/package-lock.json b/package-lock.json
index c037956..5303845 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "ISC",
"dependencies": {
- "digiid-ts": "^2.0.4",
+ "digiid-ts": "^3.0.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"qrcode": "^1.5.3",
@@ -31,12 +31,17 @@
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.5.2",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"prettier": "^3.5.3",
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
- "vite": "^6.2.6"
+ "vite": "^6.2.6",
+ "vitest": "^4.1.7"
+ },
+ "engines": {
+ "node": ">=20.19.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -1088,9 +1093,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
@@ -1106,12 +1111,12 @@
}
},
"node_modules/@noble/curves": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
- "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz",
+ "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==",
"license": "MIT",
"dependencies": {
- "@noble/hashes": "2.0.1"
+ "@noble/hashes": "2.2.0"
},
"engines": {
"node": ">= 20.19.0"
@@ -1120,25 +1125,13 @@
"url": "https://paulmillr.com/funding/"
}
},
- "node_modules/@noble/curves/node_modules/@noble/hashes": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
- "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
- "license": "MIT",
- "engines": {
- "node": ">= 20.19.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@noble/hashes": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
- "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
+ "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"license": "MIT",
"engines": {
- "node": "^14.21.3 || >=16"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
@@ -1545,6 +1538,13 @@
"win32"
]
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -1629,6 +1629,17 @@
"@types/node": "*"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -1639,6 +1650,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/dotenv": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz",
@@ -1992,6 +2010,119 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
+ "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.7",
+ "@vitest/utils": "4.1.7",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
+ "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
+ "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
+ "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.7",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
+ "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.7",
+ "@vitest/utils": "4.1.7",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
+ "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
+ "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.7",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -2252,6 +2383,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/async-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
@@ -2519,6 +2660,16 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2836,16 +2987,16 @@
}
},
"node_modules/digiid-ts": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/digiid-ts/-/digiid-ts-2.0.4.tgz",
- "integrity": "sha512-CLdU8MNFXG6MM+iZNgOVLxTq4rv+NZvuvemuJfvwJVi6hXspaysGljVrJW9G77xG/uHKZ91aKm3nDIsU6kkpGg==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/digiid-ts/-/digiid-ts-3.0.0.tgz",
+ "integrity": "sha512-bSqW3bRoN7MekkAaXW21m7X/x94YDg+bWflRG2dzpTUh+RHVrZ9gF9UkHR1j7mjmdthCRgJqVtKQ3yZYh0Ahng==",
"license": "MIT",
"dependencies": {
- "@noble/curves": "^2.0.1",
- "@noble/hashes": "^1.8.0"
+ "@noble/curves": "^2.2.0",
+ "@noble/hashes": "^2.2.0"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=20.19.0"
}
},
"node_modules/dijkstrajs": {
@@ -3043,6 +3194,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -3322,6 +3480,16 @@
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
@@ -3490,6 +3658,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3509,6 +3687,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@@ -4778,6 +4966,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -5396,6 +5594,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -5566,6 +5775,13 @@
"node": ">=4"
}
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6416,6 +6632,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -6475,6 +6698,13 @@
"dev": true,
"license": "CC0-1.0"
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -6484,6 +6714,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -6693,6 +6930,23 @@
"url": "https://opencollective.com/synckit"
}
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6741,6 +6995,16 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -7180,6 +7444,109 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/vitest": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
+ "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.7",
+ "@vitest/mocker": "4.1.7",
+ "@vitest/pretty-format": "4.1.7",
+ "@vitest/runner": "4.1.7",
+ "@vitest/snapshot": "4.1.7",
+ "@vitest/spy": "4.1.7",
+ "@vitest/utils": "4.1.7",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.7",
+ "@vitest/browser-preview": "4.1.7",
+ "@vitest/browser-webdriverio": "4.1.7",
+ "@vitest/coverage-istanbul": "4.1.7",
+ "@vitest/coverage-v8": "4.1.7",
+ "@vitest/ui": "4.1.7",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7291,6 +7658,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
diff --git a/package.json b/package.json
index ec37a87..ae6708e 100644
--- a/package.json
+++ b/package.json
@@ -5,13 +5,14 @@
"description": "",
"type": "module",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "test": "vitest run --passWithNoTests",
"dev:frontend": "vite",
+ "build:backend": "tsc -p tsconfig.json",
"build:backend:watch": "tsc -p tsconfig.json --watch",
"dev:backend": "nodemon dist/server/server/main.js",
- "dev": "npm-run-all --parallel dev:frontend build:backend:watch dev:backend",
- "build": "tsc && vite build",
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "dev": "npm-run-all build:backend --parallel dev:frontend build:backend:watch dev:backend",
+ "build": "vite build && npm run build:backend",
+ "lint": "ESLINT_USE_FLAT_CONFIG=false eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"repository": {
@@ -25,6 +26,9 @@
"url": "https://github.com/pawelzelawski/digiid-ts-demo/issues"
},
"homepage": "https://github.com/pawelzelawski/digiid-ts-demo#readme",
+ "engines": {
+ "node": ">=20.19.0"
+ },
"devDependencies": {
"@types/dotenv": "^6.1.1",
"@types/express": "^5.0.1",
@@ -40,15 +44,17 @@
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.5.2",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"prettier": "^3.5.3",
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
- "vite": "^6.2.6"
+ "vite": "^6.2.6",
+ "vitest": "^4.1.7"
},
"dependencies": {
- "digiid-ts": "^2.0.4",
+ "digiid-ts": "^3.0.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"qrcode": "^1.5.3",
diff --git a/src/client/App.tsx b/src/client/App.tsx
index b3f0308..ad1e230 100644
--- a/src/client/App.tsx
+++ b/src/client/App.tsx
@@ -7,7 +7,7 @@ type UiState = 'initial' | 'waiting' | 'success' | 'failed';
// Define the structure for result data (success or failure)
interface ResultData {
address?: string; // Present on success
- error?: string; // Present on failure
+ error?: string; // Present on failure
addressType?: string; // Added later
}
@@ -30,7 +30,7 @@ function App() {
}
console.log(`Starting status polling for session: ${sessionId}`);
- let intervalId: any = null; // Use 'any' to avoid browser/node type conflict for setInterval return type
+ let intervalId: ReturnType<typeof setInterval> | null = null;
const checkStatus = async () => {
if (!sessionId) return; // Should not happen here, but type guard
@@ -41,19 +41,26 @@ function App() {
if (!response.ok) {
// Handle specific errors like 404 (session not found/expired)
if (response.status === 404) {
- console.warn(`Session ${sessionId} not found or expired during polling.`);
+ console.warn(
+ `Session ${sessionId} not found or expired during polling.`
+ );
setError('Session expired or could not be found.');
setUiState('failed'); // Transition to failed state
setResultData({ error: 'Session expired or could not be found.' });
} else {
- const errorData = await response.json().catch(() => ({ message: 'Error fetching status' }));
- throw new Error(errorData.message || `Server responded with ${response.status}`);
+ const errorData = await response
+ .json()
+ .catch(() => ({ message: 'Error fetching status' }));
+ throw new Error(
+ errorData.message || `Server responded with ${response.status}`
+ );
}
if (intervalId) clearInterval(intervalId); // Stop polling on error
return;
}
- const data: { status: UiState, address?: string, error?: string } = await response.json();
+ const data: { status: UiState; address?: string; error?: string } =
+ await response.json();
console.log('Received status data:', data);
// If status changed from pending, update UI and stop polling
@@ -67,10 +74,12 @@ function App() {
if (intervalId) clearInterval(intervalId);
}
// If status is still 'pending', the interval will continue
-
} catch (err) {
console.error('Error polling status:', err);
- const message = err instanceof Error ? err.message : 'An unknown error occurred during status check';
+ const message =
+ err instanceof Error
+ ? err.message
+ : 'An unknown error occurred during status check';
setError(`Status polling failed: ${message}`);
// Decide if we should stop polling or transition state on generic fetch error
// For now, let's stop polling and show error, moving to failed state
@@ -104,7 +113,11 @@ function App() {
try {
const response = await fetch('/api/digiid/start');
if (!response.ok) {
- const errorData = await response.json().catch(() => ({ message: 'Failed to start session. Server responded with status: ' + response.status }));
+ const errorData = await response.json().catch(() => ({
+ message:
+ 'Failed to start session. Server responded with status: ' +
+ response.status,
+ }));
throw new Error(errorData.message || 'Failed to start session');
}
const data = await response.json();
@@ -114,7 +127,8 @@ function App() {
setUiState('waiting'); // Move to waiting state
} catch (err) {
console.error('Error starting Digi-ID session:', err);
- const message = err instanceof Error ? err.message : 'An unknown error occurred';
+ const message =
+ err instanceof Error ? err.message : 'An unknown error occurred';
setError(`Failed to initiate Digi-ID: ${message}`);
setUiState('initial'); // Stay in initial state on error
} finally {
@@ -140,9 +154,20 @@ function App() {
{/* --- Views based on uiState --- */}
{uiState === 'initial' && (
<div className="initial-view">
- <button onClick={handleStart} disabled={isLoading} className="signin-button">
- <img src="/assets/digiid-logo.png" alt="Digi-ID Logo" width="24" height="24" />
- <span>{isLoading ? 'Generating QR...' : 'Sign in with Digi-ID'}</span>
+ <button
+ onClick={handleStart}
+ disabled={isLoading}
+ className="signin-button"
+ >
+ <img
+ src="/assets/digiid-logo.png"
+ alt="Digi-ID Logo"
+ width="24"
+ height="24"
+ />
+ <span>
+ {isLoading ? 'Generating QR...' : 'Sign in with Digi-ID'}
+ </span>
</button>
</div>
)}
@@ -152,7 +177,10 @@ function App() {
{uiState === 'waiting' && qrCodeDataUrl && (
<div className="waiting-view view-box">
<h2>Scan the QR Code</h2>
- <p>Scan the QR code below using your Digi-ID compatible mobile wallet.</p>
+ <p>
+ Scan the QR code below using your Digi-ID compatible mobile
+ wallet.
+ </p>
<img src={qrCodeDataUrl} alt="Digi-ID QR Code" width="250" />
<p>Waiting for authentication...</p>
<button onClick={handleReset}>Cancel</button>
@@ -161,14 +189,30 @@ function App() {
{uiState === 'success' && resultData?.address && (
<div className="success-view view-box">
- <svg xmlns="http://www.w3.org/2000/svg" className="checkmark-icon" viewBox="0 0 52 52">
- <circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
- <path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className="checkmark-icon"
+ viewBox="0 0 52 52"
+ >
+ <circle
+ className="checkmark__circle"
+ cx="26"
+ cy="26"
+ r="25"
+ fill="none"
+ />
+ <path
+ className="checkmark__check"
+ fill="none"
+ d="M14.1 27.2l7.1 7.2 16.7-16.8"
+ />
</svg>
<h2>Authentication Successful!</h2>
<p>Verified Address:</p>
<p className="address">{resultData.address}</p>
- <p>Address Format: {getDigiByteAddressType(resultData.address)}</p>
+ <p>
+ Address Format: {getDigiByteAddressType(resultData.address)}
+ </p>
<button onClick={handleReset}>Start Over</button>
</div>
)}
@@ -177,7 +221,8 @@ function App() {
<div className="failed-view view-box">
<h2>Authentication Failed</h2>
<p className="error-message">
- Reason: {resultData?.error || error || 'An unknown error occurred.'}
+ Reason:{' '}
+ {resultData?.error || error || 'An unknown error occurred.'}
</p>
<button onClick={handleReset}>Try Again</button>
</div>
@@ -188,14 +233,26 @@ function App() {
{/* --- Description section (Always visible below the view container) --- */}
<div className="description">
<p className="code-link">
- This application demonstrates integrating Digi-ID authentication using the <a href="https://github.com/pawelzelawski/digiid-ts" target="_blank" rel="noopener noreferrer">digiid-ts</a> library.
+ This application demonstrates integrating Digi-ID authentication using
+ the{' '}
+ <a
+ href="https://github.com/pawelzelawski/digiid-ts"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ digiid-ts
+ </a>{' '}
+ library.
</p>
<p>
- Upon successful verification, the system can identify the following DigiByte address formats:
+ Upon successful verification, the system can identify the following
+ DigiByte address formats:
</p>
<ul>
<li>Legacy Addresses (P2PKH) - starting with 'D'</li>
- <li>Pay-to-Script-Hash Addresses (P2SH) - commonly starting with 'S'</li>
+ <li>
+ Pay-to-Script-Hash Addresses (P2SH) - commonly starting with 'S'
+ </li>
<li>Segregated Witness (SegWit) Addresses - starting with 'dgb1'</li>
</ul>
</div>
@@ -203,4 +260,4 @@ function App() {
);
}
-export default App; \ No newline at end of file
+export default App;
diff --git a/src/client/main.tsx b/src/client/main.tsx
index b895c80..0896e5e 100644
--- a/src/client/main.tsx
+++ b/src/client/main.tsx
@@ -7,4 +7,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
-); \ No newline at end of file
+);
diff --git a/src/client/utils.ts b/src/client/utils.ts
index 6a95f7b..1677749 100644
--- a/src/client/utils.ts
+++ b/src/client/utils.ts
@@ -1,6 +1,10 @@
// Utility to determine DigiByte address type based on prefix
-export type DigiByteAddressFormat = 'Legacy (P2PKH)' | 'Script (P2SH)' | 'SegWit (Bech32)' | 'Unknown';
+export type DigiByteAddressFormat =
+ | 'Legacy (P2PKH)'
+ | 'Script (P2SH)'
+ | 'SegWit (Bech32)'
+ | 'Unknown';
/**
* Determines the format of a DigiByte address based on its prefix.
@@ -11,21 +15,24 @@ export type DigiByteAddressFormat = 'Legacy (P2PKH)' | 'Script (P2SH)' | 'SegWit
* @param address The DigiByte address string.
* @returns The determined address format.
*/
-export function getDigiByteAddressType(address: string | undefined | null): DigiByteAddressFormat {
+export function getDigiByteAddressType(
+ address: string | undefined | null
+): DigiByteAddressFormat {
if (!address) {
return 'Unknown';
}
if (address.startsWith('dgb1')) {
return 'SegWit (Bech32)';
}
- if (address.startsWith('S')) { // Common prefix for P2SH on DGB
- return 'Script (P2SH)';
+ if (address.startsWith('S')) {
+ // Common prefix for P2SH on DGB
+ return 'Script (P2SH)';
}
if (address.startsWith('D')) {
- return 'Legacy (P2PKH)';
+ return 'Legacy (P2PKH)';
}
// If it doesn't match known prefixes, return Unknown
// Could potentially add DigiAsset checks here if they have distinct prefixes
return 'Unknown';
-} \ No newline at end of file
+}
diff --git a/src/server/main.ts b/src/server/main.ts
index 96afb0e..1ab9004 100644
--- a/src/server/main.ts
+++ b/src/server/main.ts
@@ -28,157 +28,199 @@ console.log(`Attempting to listen on port: ${PORT}`);
console.log(`Configured PUBLIC_URL: ${process.env.PUBLIC_URL}`);
// Endpoint to initiate the DigiID authentication flow
-app.get('/api/digiid/start', (req: Request, res: Response, next: NextFunction) => {
- (async () => {
- try {
- const sessionId = randomBytes(16).toString('hex');
- const nonce = randomBytes(16).toString('hex');
-
- const publicUrl = process.env.PUBLIC_URL;
- if (!publicUrl) {
- console.error('PUBLIC_URL environment variable is not set.');
- return res.status(500).json({ error: 'Server configuration error: PUBLIC_URL is missing.' });
- }
-
- let callbackUrl: string;
+app.get(
+ '/api/digiid/start',
+ (req: Request, res: Response, next: NextFunction) => {
+ (async () => {
try {
- const baseUrl = new URL(publicUrl);
- callbackUrl = new URL('/api/digiid/callback', baseUrl).toString();
+ const sessionId = randomBytes(16).toString('hex');
+ const nonce = randomBytes(16).toString('hex');
+
+ const publicUrl = process.env.PUBLIC_URL;
+ if (!publicUrl) {
+ console.error('PUBLIC_URL environment variable is not set.');
+ return res.status(500).json({
+ error: 'Server configuration error: PUBLIC_URL is missing.',
+ });
+ }
+
+ let callbackUrl: string;
+ try {
+ const baseUrl = new URL(publicUrl);
+ callbackUrl = new URL('/api/digiid/callback', baseUrl).toString();
+ } catch (error) {
+ console.error('Invalid PUBLIC_URL format:', publicUrl, error);
+ return res.status(500).json({
+ error: 'Server configuration error: Invalid PUBLIC_URL format.',
+ });
+ }
+
+ const unsecure = callbackUrl.startsWith('http://');
+ // Use the actual function from digiid-ts
+ const digiIdUri = generateDigiIDUri({ callbackUrl, unsecure, nonce });
+ console.log(
+ `Generated DigiID URI: ${digiIdUri} for session ${sessionId}`
+ );
+
+ sessionStore.set(sessionId, { nonce, status: 'pending' });
+ nonceToSessionMap.set(nonce, sessionId);
+ console.log(`Stored pending session: ${sessionId}, nonce: ${nonce}`);
+
+ const qrCodeDataUrl = await qrcode.toDataURL(digiIdUri);
+ res.json({ sessionId, qrCodeDataUrl });
} catch (error) {
- console.error('Invalid PUBLIC_URL format:', publicUrl, error);
- return res.status(500).json({ error: 'Server configuration error: Invalid PUBLIC_URL format.' });
+ console.error('Error in /api/digiid/start:', error);
+ // Ensure response is sent even on error
+ if (!res.headersSent) {
+ // Check if response hasn't already been sent
+ if (error instanceof Error) {
+ res
+ .status(400)
+ .json({ error: `Failed to generate URI: ${error.message}` });
+ } else {
+ res
+ .status(500)
+ .json({ error: 'Internal server error during start' });
+ }
+ }
}
+ })().catch(next);
+ }
+);
- const unsecure = callbackUrl.startsWith('http://');
- // Use the actual function from digiid-ts
- const digiIdUri = generateDigiIDUri({ callbackUrl, unsecure, nonce });
- console.log(`Generated DigiID URI: ${digiIdUri} for session ${sessionId}`);
+// Callback endpoint for the DigiID mobile app
+app.post(
+ '/api/digiid/callback',
+ (req: Request, res: Response, next: NextFunction) => {
+ (async () => {
+ const { address, uri, signature } = req.body;
+
+ // Basic validation of received data
+ if (!address || !uri || !signature) {
+ console.warn('Callback missing required fields.', {
+ address,
+ uri,
+ signature,
+ });
+ // Wallet doesn't expect a body on failure, just non-200. Status only for logging/debug.
+ return res.status(400).send('Missing required callback parameters.');
+ }
- sessionStore.set(sessionId, { nonce, status: 'pending' });
- nonceToSessionMap.set(nonce, sessionId);
- console.log(`Stored pending session: ${sessionId}, nonce: ${nonce}`);
+ const callbackData = { address, uri, signature };
+ console.log('Received callback:', callbackData);
- const qrCodeDataUrl = await qrcode.toDataURL(digiIdUri);
- res.json({ sessionId, qrCodeDataUrl });
+ // --- Nonce Extraction and Session Lookup ---
+ let receivedNonce: string | null = null;
+ try {
+ // DigiID URIs need scheme replaced for standard URL parsing
+ const parsableUri = uri.replace(/^digiid:/, 'http:');
+ const parsedUri = new URL(parsableUri);
+ receivedNonce = parsedUri.searchParams.get('x');
+ } catch (error) {
+ console.warn('Error parsing received URI:', uri, error);
+ return res.status(400).send('Invalid URI format.');
+ }
- } catch (error) {
- console.error('Error in /api/digiid/start:', error);
- // Ensure response is sent even on error
- if (!res.headersSent) { // Check if response hasn't already been sent
- if (error instanceof Error) {
- res.status(400).json({ error: `Failed to generate URI: ${error.message}` });
- } else {
- res.status(500).json({ error: 'Internal server error during start' });
- }
+ if (!receivedNonce) {
+ console.warn('Nonce (x parameter) not found in received URI:', uri);
+ return res.status(400).send('Nonce not found in URI.');
}
- }
- })().catch(next);
-});
-// Callback endpoint for the DigiID mobile app
-app.post('/api/digiid/callback', (req: Request, res: Response, next: NextFunction) => {
- (async () => {
- const { address, uri, signature } = req.body;
-
- // Basic validation of received data
- if (!address || !uri || !signature) {
- console.warn('Callback missing required fields.', { address, uri, signature });
- // Wallet doesn't expect a body on failure, just non-200. Status only for logging/debug.
- return res.status(400).send('Missing required callback parameters.');
- }
-
- const callbackData = { address, uri, signature };
- console.log('Received callback:', callbackData);
-
- // --- Nonce Extraction and Session Lookup ---
- let receivedNonce: string | null = null;
- try {
- // DigiID URIs need scheme replaced for standard URL parsing
- const parsableUri = uri.replace(/^digiid:/, 'http:');
- const parsedUri = new URL(parsableUri);
- receivedNonce = parsedUri.searchParams.get('x');
- } catch (error) {
- console.warn('Error parsing received URI:', uri, error);
- return res.status(400).send('Invalid URI format.');
- }
-
- if (!receivedNonce) {
- console.warn('Nonce (x parameter) not found in received URI:', uri);
- return res.status(400).send('Nonce not found in URI.');
- }
-
- const sessionId = nonceToSessionMap.get(receivedNonce);
- if (!sessionId) {
- console.warn('Session not found for received nonce:', receivedNonce);
- // Nonce might be expired or invalid
- return res.status(404).send('Session not found or expired for this nonce.');
- }
-
- // Retrieve the session *before* the try/finally block for verification
- const session = sessionStore.get(sessionId);
- if (!session) {
- console.error(`Critical: Session data missing for ${sessionId} despite nonce match.`);
- if (!res.headersSent) res.status(500).send('Internal server error: Session data missing.');
+ const sessionId = nonceToSessionMap.get(receivedNonce);
+ if (!sessionId) {
+ console.warn('Session not found for received nonce:', receivedNonce);
+ // Nonce might be expired or invalid
+ return res
+ .status(404)
+ .send('Session not found or expired for this nonce.');
+ }
+
+ // Retrieve the session *before* the try/finally block for verification
+ const session = sessionStore.get(sessionId);
+ if (!session) {
+ console.error(
+ `Critical: Session data missing for ${sessionId} despite nonce match.`
+ );
+ if (!res.headersSent)
+ res.status(500).send('Internal server error: Session data missing.');
return; // Explicitly return void
- }
- if (session.status !== 'pending') {
+ }
+ if (session.status !== 'pending') {
console.warn('Session already processed:', sessionId, session.status);
- if (!res.headersSent) res.status(200).send('Session already processed.');
+ if (!res.headersSent)
+ res.status(200).send('Session already processed.');
+ return; // Explicitly return void
+ }
+
+ // --- Verification ---
+ let expectedCallbackUrl: string;
+ try {
+ const publicUrl = process.env.PUBLIC_URL;
+ if (!publicUrl)
+ throw new Error(
+ 'PUBLIC_URL environment variable is not configured on the server.'
+ );
+ expectedCallbackUrl = new URL(
+ '/api/digiid/callback',
+ publicUrl
+ ).toString();
+ } catch (error) {
+ console.error(
+ 'Server configuration error constructing expected callback URL:',
+ error
+ );
+ session.status = 'failed';
+ session.error = 'Server configuration error preventing verification.';
+ sessionStore.set(sessionId, session);
+ // Respond 200 OK as per protocol
+ if (!res.headersSent) res.status(200).send();
return; // Explicitly return void
- }
-
- // --- Verification ---
- let expectedCallbackUrl: string;
- try {
- const publicUrl = process.env.PUBLIC_URL;
- if (!publicUrl) throw new Error('PUBLIC_URL environment variable is not configured on the server.');
- expectedCallbackUrl = new URL('/api/digiid/callback', publicUrl).toString();
- } catch (error) {
- console.error('Server configuration error constructing expected callback URL:', error);
- session.status = 'failed';
- session.error = 'Server configuration error preventing verification.';
- sessionStore.set(sessionId, session);
- // Respond 200 OK as per protocol
- if (!res.headersSent) res.status(200).send();
- return; // Explicitly return void
- }
-
- const verifyOptions = { expectedCallbackUrl, expectedNonce: session.nonce };
-
- try {
- console.log('Attempting to verify callback with:', { callbackData, verifyOptions });
- await verifyDigiIDCallback(callbackData, verifyOptions);
-
- // Success case (no throw from verifyDigiIDCallback)
- console.log(`Verification successful for session ${sessionId}, address: ${address}`);
- session.status = 'success';
- session.address = address; // Store the verified address
- session.error = undefined; // Clear any previous error
- nonceToSessionMap.delete(session.nonce); // Clean up nonce map only on success
-
- } catch (error) {
- // Failure case (verifyDigiIDCallback threw an error)
- console.warn(`Verification failed for session ${sessionId}:`, error);
- session.status = 'failed';
- if (error instanceof Error) {
- session.error = error.message;
- } else {
- session.error = 'An unknown verification error occurred.';
}
- // Optionally cleanup nonce map on failure too, depending on policy
- // nonceToSessionMap.delete(session.nonce);
- } finally {
- // Update store and respond 200 OK in all cases (after try/catch)
- sessionStore.set(sessionId, session);
- console.log(`Final session state for ${sessionId}:`, session);
- // Ensure response is sent if not already done (e.g. in case of unexpected error before finally)
- if (!res.headersSent) {
+
+ const verifyOptions = {
+ expectedCallbackUrl,
+ expectedNonce: session.nonce,
+ };
+
+ try {
+ console.log('Attempting to verify callback with:', {
+ callbackData,
+ verifyOptions,
+ });
+ await verifyDigiIDCallback(callbackData, verifyOptions);
+
+ // Success case (no throw from verifyDigiIDCallback)
+ console.log(
+ `Verification successful for session ${sessionId}, address: ${address}`
+ );
+ session.status = 'success';
+ session.address = address; // Store the verified address
+ session.error = undefined; // Clear any previous error
+ nonceToSessionMap.delete(session.nonce); // Clean up nonce map only on success
+ } catch (error) {
+ // Failure case (verifyDigiIDCallback threw an error)
+ console.warn(`Verification failed for session ${sessionId}:`, error);
+ session.status = 'failed';
+ if (error instanceof Error) {
+ session.error = error.message;
+ } else {
+ session.error = 'An unknown verification error occurred.';
+ }
+ // Optionally cleanup nonce map on failure too, depending on policy
+ // nonceToSessionMap.delete(session.nonce);
+ } finally {
+ // Update store and respond 200 OK in all cases (after try/catch)
+ sessionStore.set(sessionId, session);
+ console.log(`Final session state for ${sessionId}:`, session);
+ // Ensure response is sent if not already done (e.g. in case of unexpected error before finally)
+ if (!res.headersSent) {
res.status(200).send();
+ }
+ // No explicit return needed here as it's the end of the function
}
- // No explicit return needed here as it's the end of the function
- }
- })().catch(next);
-});
+ })().catch(next);
+ }
+);
// Endpoint to check the status of an authentication session
app.get('/api/digiid/status/:sessionId', (req: Request, res: Response) => {
@@ -200,4 +242,4 @@ app.get('/', (_: Request, res: Response) => {
// Start the server
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
-}); \ No newline at end of file
+});
diff --git a/src/server/utils.ts b/src/server/utils.ts
index 0b1333f..bb614c0 100644
--- a/src/server/utils.ts
+++ b/src/server/utils.ts
@@ -1,6 +1,9 @@
// Simple utility to determine DigiByte address type based on prefix
-export type DigiByteAddressType = 'DigiByte (DGB)' | 'DigiAsset (DGA)' | 'Unknown';
+export type DigiByteAddressType =
+ | 'DigiByte (DGB)'
+ | 'DigiAsset (DGA)'
+ | 'Unknown';
export function getDigiByteAddressType(address: string): DigiByteAddressType {
if (address.startsWith('dgb1')) {
@@ -10,11 +13,12 @@ export function getDigiByteAddressType(address: string): DigiByteAddressType {
// For now, assume non-DGB is DigiAsset, but this might need refinement
// depending on actual DigiAsset address formats.
// If the digiid-ts library provides a helper for this, use that instead.
- else if (address) { // Basic check to differentiate from empty/null
+ else if (address) {
+ // Basic check to differentiate from empty/null
// Assuming DigiAssets might start differently or be the fallback
// This is a placeholder assumption.
// A more robust check based on DigiAsset address specification is needed.
return 'DigiAsset (DGA)'; // Placeholder - ADJUST BASED ON ACTUAL DGA PREFIX
}
return 'Unknown';
-} \ No newline at end of file
+}