diff --git a/package-lock.json b/package-lock.json index 2610572..b00c9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "demo-rp-api", "version": "0.0.0", "dependencies": { + "@simplewebauthn/server": "^10.0.1", "hono": "^4.5.4", "zod": "^3.23.8", "zod-form-data": "^2.0.2" @@ -15,6 +16,7 @@ "devDependencies": { "@cloudflare/vitest-pool-workers": "^0.4.5", "@cloudflare/workers-types": "^4.20240729.0", + "@simplewebauthn/types": "^10.0.0", "typescript": "^5.5.2", "vitest": "1.5.0", "wrangler": "^3.70.0" @@ -33,18 +35,18 @@ } }, "node_modules/@cloudflare/vitest-pool-workers": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.4.17.tgz", - "integrity": "sha512-ddSKdWvYCdQ8p01NF9iIjOMDQVqexWcSFG38EWhSJeNuPiiYE0/fjV2pngjtrkRWk47CDpHg166W0XblPxPfXw==", + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.4.21.tgz", + "integrity": "sha512-CbETvL1rdmVF04aFlMVg13a0D9pWmSTt3+ena3bLgl5EjebwGQVFk9Qjrl76OemDc9hP7PJtsb0WCmEcDYeV2A==", "dev": true, "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^4.3.0", "esbuild": "0.17.19", - "miniflare": "3.20240725.0", + "miniflare": "3.20240806.0", "semver": "^7.5.1", - "wrangler": "3.68.0", + "wrangler": "3.70.0", "zod": "^3.22.3" }, "peerDependencies": { @@ -53,53 +55,10 @@ "vitest": "1.3.x - 1.5.x" } }, - "node_modules/@cloudflare/vitest-pool-workers/node_modules/wrangler": { - "version": "3.68.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.68.0.tgz", - "integrity": "sha512-gsIeglkh5nOn1mHJs0bf1pOq/DvIt+umjO/5a867IYYXaN4j/ar5cRR1+F5ue3S7uEjYCLIZZjs8ESiPTSEt+Q==", - "dev": true, - "dependencies": { - "@cloudflare/kv-asset-handler": "0.3.4", - "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "blake3-wasm": "^2.1.5", - "chokidar": "^3.5.3", - "date-fns": "^3.6.0", - "esbuild": "0.17.19", - "miniflare": "3.20240725.0", - "nanoid": "^3.3.3", - "path-to-regexp": "^6.2.0", - "resolve": "^1.22.8", - "resolve.exports": "^2.0.2", - "selfsigned": "^2.0.1", - "source-map": "^0.6.1", - "unenv": "npm:unenv-nightly@1.10.0-1717606461.a117952", - "workerd": "1.20240725.0", - "xxhash-wasm": "^1.0.1" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.17.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20240725.0" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240725.0.tgz", - "integrity": "sha512-KpE7eycdZ9ON+tKBuTyqZh8SdFWHGrh2Ru9LcbpeFwb7O9gDQv9ceSdoV/T598qlT0a0yVKM62R6xa5ec0UOWA==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240806.0.tgz", + "integrity": "sha512-FqcVBBCO//I39K5F+HqE/v+UkqY1UrRnS653Jv+XsNNH9TpX5fTs7VCKG4kDSnmxlAaKttyIN5sMEt7lpuNExQ==", "cpu": [ "x64" ], @@ -113,9 +72,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240725.0.tgz", - "integrity": "sha512-/UQlI04FdXLpPlDzzsWGz8TuKrMZKLowTo+8PkxgEiWIaBhE4DIDM5bwj3jM4Bs8yOLiL2ovQNpld5CnAKqA8g==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240806.0.tgz", + "integrity": "sha512-8c3KvmzYp/wg+82KHSOzDetJK+pThH4MTrU1OsjmsR2cUfedm5dk5Lah9/0Ld68+6A0umFACi4W2xJHs/RoBpA==", "cpu": [ "arm64" ], @@ -129,9 +88,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240725.0.tgz", - "integrity": "sha512-Z5t12qYLvHz0b3ZRBBm2HQ93RiHrAnjFfdhtjMcgJypAGkiWpOCEn2xar/WqDhMfqnk0sa8aYiYAbMAlP1WN6w==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240806.0.tgz", + "integrity": "sha512-/149Bpxw4e2p5QqnBc06g0mx+4sZYh9j0doilnt0wk/uqYkLp0DdXGMQVRB74sBLg2UD3wW8amn1w3KyFhK2tQ==", "cpu": [ "x64" ], @@ -145,9 +104,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240725.0.tgz", - "integrity": "sha512-j9gYXLOwOyNehLMzP7KxQ+Y6/nxcL9i6LTDJC6RChoaxLRbm0Y/9Otu+hfyzeNeRpt31ip6vqXZ1QQk6ygzI8A==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240806.0.tgz", + "integrity": "sha512-lacDWY3S1rKL/xT6iMtTQJEKmTTKrBavPczInEuBFXElmrS6IwVjZwv8hhVm32piyNt/AuFu9BYoJALi9D85/g==", "cpu": [ "arm64" ], @@ -161,9 +120,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240725.0.tgz", - "integrity": "sha512-fkrJLWNN6rrPjZ0eKJx328NVMo4BsainKxAfqaPMEd6uRwjOM8uN8V4sSLsXXP8GQMAx6hAG2hU86givS4GItg==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240806.0.tgz", + "integrity": "sha512-hC6JEfTSQK6//Lg+D54TLVn1ceTPY+fv4MXqDZIYlPP53iN+dL8Xd0utn2SG57UYdlL5FRAhm/EWHcATZg1RgA==", "cpu": [ "x64" ], @@ -599,6 +558,11 @@ "node": ">=14" } }, + "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==" + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -636,6 +600,65 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@levischuck/tiny-cbor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.2.tgz", + "integrity": "sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==" + }, + "node_modules/@peculiar/asn1-android": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.13.tgz", + "integrity": "sha512-0VTNazDGKrLS6a3BwTDZanqq6DR/I3SbvmDMuS8Be+OYpvM6x1SRDh9AGDsHVnaCOIztOspCPc6N1m+iUv1Xxw==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.13.tgz", + "integrity": "sha512-3dF2pQcrN/WJEMq+9qWLQ0gqtn1G81J4rYqFl6El6QV367b4IuhcRv+yMA84tNNyHOJn9anLXV5radnpPiG3iA==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.13.tgz", + "integrity": "sha512-wBNQqCyRtmqvXkGkL4DR3WxZhHy8fDiYtOjTeCd7SFE5F6GBeafw3EJ94PX/V0OJJrjQ40SkRY2IZu3ZSyBqcg==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", + "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.13.tgz", + "integrity": "sha512-PfeLQl2skXmxX2/AFFCVaWU8U6FKW1Db43mgBhShCOFS1bVxqtvusq1hVjfuEcuSQGedrLdCSvTgabluwN/M9A==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "ipaddr.js": "^2.1.0", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", @@ -844,6 +867,30 @@ "win32" ] }, + "node_modules/@simplewebauthn/server": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-10.0.1.tgz", + "integrity": "sha512-djNWcRn+H+6zvihBFJSpG3fzb0NQS9c/Mw5dYOtZ9H+oDw8qn9Htqxt4cpqRvSOAfwqP7rOvE9rwqVaoGGc3hg==", + "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", + "@simplewebauthn/types": "^10.0.0", + "cross-fetch": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@simplewebauthn/types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-10.0.0.tgz", + "integrity": "sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1019,6 +1066,19 @@ "printable-characters": "^1.0.42" } }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1170,6 +1230,14 @@ "node": ">= 0.6" } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1456,6 +1524,14 @@ "node": ">=16.17.0" } }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1602,9 +1678,9 @@ } }, "node_modules/miniflare": { - "version": "3.20240725.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240725.0.tgz", - "integrity": "sha512-n9NTLI8J9Xt0Cls6dRpqoIPkVFnxD9gMnU/qDkDX9diKfN16HyxpAdA5mto/hKuRpjW19TxnTMcxBo90vZXemw==", + "version": "3.20240806.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240806.0.tgz", + "integrity": "sha512-jDsXBJOLUVpIQXHsluX3xV0piDxXolTCsxdje2Ex2LTC9PsSoBIkMwvCmnCxe9wpJJCq8rb0UMyeEn3KOF3LOw==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -1615,7 +1691,7 @@ "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", - "workerd": "1.20240725.0", + "workerd": "1.20240806.0", "ws": "^8.17.1", "youch": "^3.2.2", "zod": "^3.22.3" @@ -1672,6 +1748,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-fetch-native": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", @@ -1866,6 +1961,22 @@ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "dev": true }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -2189,11 +2300,15 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/type-detect": { "version": "4.1.0", @@ -2831,6 +2946,20 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2863,9 +2992,9 @@ } }, "node_modules/workerd": { - "version": "1.20240725.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240725.0.tgz", - "integrity": "sha512-VZwgejRcHsQ9FEPtc7v25ebINLAR+stL3q1hC1xByE+quskdoWpTXHkZwZ3IdSgvm9vPVbCbJw9p5mGnDByW2A==", + "version": "1.20240806.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240806.0.tgz", + "integrity": "sha512-yyNtyzTMgVY0sgYijHBONqZFVXsOFGj2jDjS8MF/RbO2ZdGROvs4Hkc/9QnmqFWahE0STxXeJ1yW1yVotdF0UQ==", "dev": true, "hasInstallScript": true, "bin": { @@ -2875,11 +3004,11 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20240725.0", - "@cloudflare/workerd-darwin-arm64": "1.20240725.0", - "@cloudflare/workerd-linux-64": "1.20240725.0", - "@cloudflare/workerd-linux-arm64": "1.20240725.0", - "@cloudflare/workerd-windows-64": "1.20240725.0" + "@cloudflare/workerd-darwin-64": "1.20240806.0", + "@cloudflare/workerd-darwin-arm64": "1.20240806.0", + "@cloudflare/workerd-linux-64": "1.20240806.0", + "@cloudflare/workerd-linux-arm64": "1.20240806.0", + "@cloudflare/workerd-windows-64": "1.20240806.0" } }, "node_modules/wrangler": { @@ -2926,132 +3055,6 @@ } } }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240806.0.tgz", - "integrity": "sha512-FqcVBBCO//I39K5F+HqE/v+UkqY1UrRnS653Jv+XsNNH9TpX5fTs7VCKG4kDSnmxlAaKttyIN5sMEt7lpuNExQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240806.0.tgz", - "integrity": "sha512-8c3KvmzYp/wg+82KHSOzDetJK+pThH4MTrU1OsjmsR2cUfedm5dk5Lah9/0Ld68+6A0umFACi4W2xJHs/RoBpA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240806.0.tgz", - "integrity": "sha512-/149Bpxw4e2p5QqnBc06g0mx+4sZYh9j0doilnt0wk/uqYkLp0DdXGMQVRB74sBLg2UD3wW8amn1w3KyFhK2tQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240806.0.tgz", - "integrity": "sha512-lacDWY3S1rKL/xT6iMtTQJEKmTTKrBavPczInEuBFXElmrS6IwVjZwv8hhVm32piyNt/AuFu9BYoJALi9D85/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240806.0.tgz", - "integrity": "sha512-hC6JEfTSQK6//Lg+D54TLVn1ceTPY+fv4MXqDZIYlPP53iN+dL8Xd0utn2SG57UYdlL5FRAhm/EWHcATZg1RgA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/wrangler/node_modules/miniflare": { - "version": "3.20240806.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240806.0.tgz", - "integrity": "sha512-jDsXBJOLUVpIQXHsluX3xV0piDxXolTCsxdje2Ex2LTC9PsSoBIkMwvCmnCxe9wpJJCq8rb0UMyeEn3KOF3LOw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "stoppable": "^1.1.0", - "undici": "^5.28.4", - "workerd": "1.20240806.0", - "ws": "^8.17.1", - "youch": "^3.2.2", - "zod": "^3.22.3" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/wrangler/node_modules/workerd": { - "version": "1.20240806.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240806.0.tgz", - "integrity": "sha512-yyNtyzTMgVY0sgYijHBONqZFVXsOFGj2jDjS8MF/RbO2ZdGROvs4Hkc/9QnmqFWahE0STxXeJ1yW1yVotdF0UQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20240806.0", - "@cloudflare/workerd-darwin-arm64": "1.20240806.0", - "@cloudflare/workerd-linux-64": "1.20240806.0", - "@cloudflare/workerd-linux-arm64": "1.20240806.0", - "@cloudflare/workerd-windows-64": "1.20240806.0" - } - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package.json b/package.json index e5e0c00..fa49c8e 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ "devDependencies": { "@cloudflare/vitest-pool-workers": "^0.4.5", "@cloudflare/workers-types": "^4.20240729.0", + "@simplewebauthn/types": "^10.0.0", "typescript": "^5.5.2", "vitest": "1.5.0", "wrangler": "^3.70.0" }, "dependencies": { + "@simplewebauthn/server": "^10.0.1", "hono": "^4.5.4", "zod": "^3.23.8", "zod-form-data": "^2.0.2" diff --git a/src/handlers/handleCreateRegOptions.ts b/src/handlers/handleCreateRegOptions.ts index 76d7e37..2ca1c81 100644 --- a/src/handlers/handleCreateRegOptions.ts +++ b/src/handlers/handleCreateRegOptions.ts @@ -1,14 +1,66 @@ import { Context } from 'hono'; +import { HTTPException } from 'hono/http-exception'; import { zfd } from 'zod-form-data'; +import { generateRegistrationOptions } from '@simplewebauthn/server'; +import { cose, isoBase64URL } from '@simplewebauthn/server/helpers'; -import { regOptionsInputSchema } from '../schemas'; +import { ZodError, regOptionsInputSchema } from '../schemas'; /** * Generate registration options */ export async function handleCreateRegOptions(context: Context): Promise { - const parsedInput = zfd.formData(regOptionsInputSchema).parse(context.req.query()); - console.log(parsedInput); - return context.text(`handleCreateRegOptions ${JSON.stringify(parsedInput)}`); + // Parse options from query params + let parsedInput; + try { + parsedInput = zfd.formData(regOptionsInputSchema).parse(context.req.query()); + } catch (err) { + const _err = err as ZodError; + throw new HTTPException(400, { message: JSON.stringify(_err.errors) }); + } + + const { + algES256, + algRS256, + attestation, + discoverableCredential, + userVerification, + userName, + userID, + attachment, + } = parsedInput; + + const { RP_ID, RP_NAME } = context.env; + + let supportedAlgorithmIDs = []; + + if (algES256) { + supportedAlgorithmIDs.push(cose.COSEALG.ES256); + } + + if (algRS256) { + supportedAlgorithmIDs.push(cose.COSEALG.RS256); + } + + let userIDBytes = undefined; + if (userID) { + userIDBytes = isoBase64URL.toBuffer(userID); + } + + const opts = await generateRegistrationOptions({ + rpID: RP_ID, + rpName: RP_NAME, + userName, + userID: userIDBytes, + attestationType: attestation, + authenticatorSelection: { + authenticatorAttachment: attachment, + residentKey: discoverableCredential, + userVerification, + }, + supportedAlgorithmIDs, + }); + + return context.json(opts); } diff --git a/src/schemas.ts b/src/schemas.ts index bd794ca..aec832f 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { isoBase64URL } from '@simplewebauthn/server/helpers'; /** @@ -6,11 +7,66 @@ import { z } from 'zod'; */ export { ZodError } from 'zod'; + +/** + * Zod preprocessors + */ + +/** + * Boolean values in query params are actually strings, so coerce them to proper booleans + */ +function castToBoolean() { + return z.preprocess((value) => { + if (typeof value === 'boolean') { + return value; + } + + const _valLower = String(value).toLowerCase(); + + if (_valLower === 'true') { + return true; + } + + if (_valLower === 'false') { + return false; + } + + return value; + }, z.boolean()); +} + +/** + * Ensure this string value is a valid base64url string + */ +function castToBase64urlString() { + return z.preprocess((value, ctx) => { + if (!isoBase64URL.isBase64URL(String(value))) { + throw new z.ZodError([ + { + code: z.ZodIssueCode.invalid_string, + path: ctx.path, + message: 'not a valid base64url string', + validation: 'base64', + } + ]); + } + + return value; + }, z.string().base64()); +} + /** * Schema for incoming query params to configure registration options */ export const regOptionsInputSchema = z.object({ - foo: z.string().optional(), + userName: z.string(), + userID: castToBase64urlString().optional(), + userVerification: z.enum(['discouraged', 'preferred', 'required']).default('preferred'), + attestation: z.enum(['none', 'direct']).default('none'), + attachment: z.enum(['cross-platform', 'platform']).optional(), + algES256: castToBoolean().default(true), + algRS256: castToBoolean().default(true), + discoverableCredential: z.enum(['discouraged', 'preferred', 'required']).default('required'), }); diff --git a/test/index.spec.ts b/test/index.spec.ts index 8329acd..ea27d75 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,35 +1,15 @@ -// test/index.spec.ts -import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; +import { SELF } from 'cloudflare:test'; import { describe, it, expect } from 'vitest'; -// import worker from '../src/index'; - -// For now, you'll need to do something like this to get a correctly-typed -// `Request` to pass to `worker.fetch()`. -// const IncomingRequest = Request; +import { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types'; describe('Routing tests', () => { - // it('responds with Hello World! (unit style)', async () => { - // const request = new IncomingRequest('http://example.com'); - // // Create an empty context to pass to `worker.fetch()`. - // const ctx = createExecutionContext(); - // const response = await worker.fetch(request, env, ctx); - // // Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions - // await waitOnExecutionContext(ctx); - // expect(await response.text()).toMatchInlineSnapshot(`"Hello from passkeys.dev RP demo API!"`); - // }); - - // it('responds with Hello World! (integration style)', async () => { - // const response = await SELF.fetch('https://example.com'); - // expect(await response.text()).toMatchInlineSnapshot(`"Hello from passkeys.dev RP demo API!"`); - // }); - it('recognizes GET /registration/options', async () => { - const response = await SELF.fetch('https://example.com/registration/options', { method: 'GET' }); + const response = await SELF.fetch('https://example.com/registration/options?userName=foo'); expect(response.status).toBe(200); }); it('recognizes GET /authentication/options', async () => { - const response = await SELF.fetch('https://example.com/authentication/options', { method: 'GET' }); + const response = await SELF.fetch('https://example.com/authentication/options'); expect(response.status).toBe(200); }); @@ -48,3 +28,211 @@ describe('Routing tests', () => { expect(response.status).toBe(404); }); }); + +describe('Registration options', () => { + it('requires username', async () => { + const response = await SELF.fetch('https://example.com/registration/options'); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("userName"); + }); + + it('generates basic options', async () => { + const username = 'mmiller'; + const response = await SELF.fetch(`https://example.com/registration/options?userName=${username}`); + expect(response.status).toBe(200); + expect(response.headers.get('Content-Type')).toMatch('application/json'); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.challenge).toBeTypeOf('string'); + expect(opts.rp.name).toEqual('passkeys.dev'); + expect(opts.rp.id).toEqual('passkeys.dev'); + expect(opts.user.id).toBeTypeOf('string'); + expect(opts.user.name).toEqual(username); + expect(opts.user.displayName).toEqual(''); + expect(opts.pubKeyCredParams).toEqual([ + { "alg": -7, "type": "public-key" }, + { "alg": -257, "type": "public-key" }, + ]); + expect(opts.timeout).toEqual(60000); + expect(opts.attestation).toEqual('none'); + expect(opts.excludeCredentials).toEqual([]); + expect(opts.authenticatorSelection?.residentKey).toEqual('required'); + expect(opts.authenticatorSelection?.userVerification).toEqual('preferred'); + expect(opts.authenticatorSelection?.requireResidentKey).toEqual(true); + expect(opts.extensions?.credProps).toEqual(true); + }); + + describe('Param: userID', () => { + it('uses specified user ID', async () => { + const userID = '1234'; + const response = await SELF.fetch( + `https://example.com/registration/options?userName=mmiller&userID=${userID}`, + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.user.id).toEqual(userID); + }); + + it('generates unique user ID when omitted', async () => { + const response1 = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller', + ); + const opts1 = await response1.json() as PublicKeyCredentialCreationOptionsJSON; + + const response2 = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller', + ); + const opts2 = await response2.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts1.user.id).not.toEqual(opts2.user.id); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&userID=mmiller@example.com', + ); + + expect(response.status).toBe(400); + expect(await response.text()).toMatch("userID"); + }); + }); + + describe('Param: algES256', () => { + it('omits ES256 when param is false', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&algES256=false', + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.pubKeyCredParams).toEqual([ + { "alg": -257, "type": "public-key" }, + ]); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&algES256=maru', + { method: 'GET' }, + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("algES256"); + }); + }); + + describe('Param: algRS256', () => { + it('omits RS256 when param is false', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&algRS256=false', + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.pubKeyCredParams).toEqual([ + { "alg": -7, "type": "public-key" }, + ]); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&algRS256=batsu', + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("algRS256"); + }); + }); + + describe('Param: attestation', () => { + it.each(['none', 'direct'])('supports value "%s"', async (val) => { + const response = await SELF.fetch( + `https://example.com/registration/options?userName=mmiller&attestation=${val}`, + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.attestation).toEqual(val); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&attestation=sometimes', + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("attestation"); + }); + }); + + describe('Param: discoverableCredential', () => { + it.each([ + 'discouraged', + 'preferred', + 'required', + ])('supports value "%s"', async (val) => { + const response = await SELF.fetch( + `https://example.com/registration/options?userName=mmiller&discoverableCredential=${val}`, + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.authenticatorSelection?.residentKey).toEqual(val); + expect(opts.authenticatorSelection?.requireResidentKey).toEqual(val === 'required'); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&discoverableCredential=sure', + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("discoverableCredential"); + }); + }); + + describe('Param: userVerification', () => { + it.each([ + 'discouraged', + 'preferred', + 'required', + ])('supports value "%s"', async (val) => { + const response = await SELF.fetch( + `https://example.com/registration/options?userName=mmiller&userVerification=${val}`, + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.authenticatorSelection?.userVerification).toEqual(val); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&userVerification=yesButNotCached', + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("userVerification"); + }); + }); + + describe('Param: attachment', () => { + it.each([ + 'cross-platform', + 'platform', + ])('supports value "%s"', async (val) => { + const response = await SELF.fetch( + `https://example.com/registration/options?userName=mmiller&attachment=${val}`, + ); + + const opts = await response.json() as PublicKeyCredentialCreationOptionsJSON; + + expect(opts.authenticatorSelection?.authenticatorAttachment).toEqual(val); + }); + + it('errors on bad param value', async () => { + const response = await SELF.fetch( + 'https://example.com/registration/options?userName=mmiller&attachment=hybrid', + ); + expect(response.status).toBe(400); + expect(await response.text()).toMatch("attachment"); + }); + }); +}); diff --git a/wrangler.toml b/wrangler.toml index 4b13bc9..39fb970 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,7 +1,7 @@ #:schema node_modules/wrangler/config-schema.json name = "demo-rp-api" main = "src/index.ts" -compatibility_date = "2024-07-29" +compatibility_date = "2024-07-25" compatibility_flags = ["nodejs_compat"] # Automatically place your workloads in an optimal location to minimize latency. @@ -16,8 +16,9 @@ compatibility_flags = ["nodejs_compat"] # - https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables # Note: Use secrets to store sensitive data. # - https://developers.cloudflare.com/workers/configuration/secrets/ -# [vars] -# MY_VARIABLE = "production_value" +[vars] +RP_ID = "passkeys.dev" +RP_NAME = "passkeys.dev" # Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflare’s global network # Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai