From 5487a23c86720c1dd15543e628b8b9a2f6a016e6 Mon Sep 17 00:00:00 2001 From: Nolan Hellyer Date: Thu, 23 Jan 2025 20:58:46 -0800 Subject: [PATCH] Added auth to server hook. --- .gitignore | 3 +- package-lock.json | 825 ++++++++++++++++++++++- package.json | 7 + src/app.d.ts | 7 +- src/hooks.server.ts | 11 +- src/lib/GameData.ts | 2 +- src/lib/GameEvent.ts | 11 +- src/lib/Id.ts | 11 + src/lib/Listing.ts | 7 +- src/lib/Login.ts | 22 + src/lib/server/Game.ts | 2 +- src/lib/server/Id.ts | 1 - src/lib/server/cache.ts | 4 +- src/lib/server/modifyListing.ts | 4 +- src/lib/server/mongo.ts | 80 +++ src/lib/server/requestTools.ts | 157 +++++ src/lib/server/responseBodies.ts | 8 + src/lib/server/routeAuth.ts | 25 + src/lib/validation.ts | 14 +- src/routes/api/games/+server.ts | 5 +- src/routes/api/games/[gameid]/+server.ts | 9 +- src/routes/api/token/+server.ts | 53 ++ src/routes/api/users/+server.ts | 40 ++ tests/requests.http | 52 +- 24 files changed, 1305 insertions(+), 55 deletions(-) create mode 100644 src/lib/Id.ts create mode 100644 src/lib/Login.ts delete mode 100644 src/lib/server/Id.ts create mode 100644 src/lib/server/mongo.ts create mode 100644 src/lib/server/requestTools.ts create mode 100644 src/lib/server/routeAuth.ts create mode 100644 src/routes/api/token/+server.ts create mode 100644 src/routes/api/users/+server.ts diff --git a/.gitignore b/.gitignore index dbbb26c..4d6ab11 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* # Development -cert \ No newline at end of file +cert +.vscode \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bfab32a..ac2709f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,12 +7,19 @@ "": { "name": "ten-thousand-client", "version": "0.0.1", + "dependencies": { + "bcrypt": "^5.1.1", + "jsonwebtoken": "^9.0.2", + "mongodb": "^6.12.0" + }, "devDependencies": { "@eslint/compat": "^1.2.3", "@eslint/js": "^9.17.0", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@types/bcrypt": "^5.0.2", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.10.7", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", @@ -711,6 +718,35 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1108,6 +1144,16 @@ "vite": "^5.0.0" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -1129,6 +1175,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", @@ -1139,6 +1195,21 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", @@ -1458,6 +1529,12 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1491,6 +1568,18 @@ "acorn": ">=8.9.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1508,6 +1597,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1524,6 +1622,26 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1565,14 +1683,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1592,6 +1722,21 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", + "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1672,6 +1817,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -1702,13 +1856,27 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -1751,7 +1919,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1792,6 +1959,21 @@ "node": ">=0.10.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/devalue": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", @@ -1799,6 +1981,21 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/es-module-lexer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", @@ -2253,6 +2450,36 @@ "dev": true, "license": "ISC" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2268,6 +2495,48 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2325,6 +2594,25 @@ "node": ">=8" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2373,6 +2661,23 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2383,6 +2688,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2457,6 +2771,49 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2531,6 +2888,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2538,6 +2931,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/loupe": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", @@ -2555,6 +2954,36 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2596,7 +3025,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2605,6 +3033,108 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mongodb": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -2629,7 +3159,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -2658,6 +3187,100 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "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/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/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==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/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==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2731,6 +3354,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2938,7 +3570,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2965,6 +3596,20 @@ ], "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", @@ -3000,6 +3645,22 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.30.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", @@ -3076,11 +3737,30 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3089,6 +3769,12 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -3126,6 +3812,12 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/sirv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", @@ -3151,6 +3843,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3165,6 +3866,41 @@ "dev": true, "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3317,6 +4053,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "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" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -3395,6 +4148,18 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ts-api-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", @@ -3479,7 +4244,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vite": { @@ -3650,6 +4414,28 @@ } } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3683,6 +4469,15 @@ "node": ">=8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3693,6 +4488,18 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index a187ffd..11f3334 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@types/bcrypt": "^5.0.2", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.10.7", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", @@ -33,5 +35,10 @@ "typescript-eslint": "^8.0.0", "vite": "^5.4.11", "vitest": "^2.0.4" + }, + "dependencies": { + "bcrypt": "^5.1.1", + "jsonwebtoken": "^9.0.2", + "mongodb": "^6.12.0" } } diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..330a6f2 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,9 +1,14 @@ // See https://svelte.dev/docs/kit/types#app.d.ts + +import type { LocalCredentials } from "$lib/server/requestTools"; + // for information about these interfaces declare global { namespace App { // interface Error {} - // interface Locals {} + interface Locals { + user: LocalCredentials; + } // interface PageData {} // interface PageState {} // interface Platform {} diff --git a/src/hooks.server.ts b/src/hooks.server.ts index abc649f..6b5c931 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,15 @@ +import { isAuthorized } from "$lib/server/requestTools"; +import { unauthorizedResponse } from "$lib/server/responseBodies"; import type { Handle } from "@sveltejs/kit"; export const handle: Handle = async ({ event, resolve }) => { - console.log("this got called", event.isSubRequest); + const auth = await isAuthorized(event); + + if (!auth) { + return unauthorizedResponse(); + } + + event.locals.user = auth; + return await resolve(event); }; diff --git a/src/lib/GameData.ts b/src/lib/GameData.ts index dba091d..084a8a6 100644 --- a/src/lib/GameData.ts +++ b/src/lib/GameData.ts @@ -1,5 +1,5 @@ import { hasOnlyKeys, hasProperty } from "./validation"; -import type { Id } from "./server/Id"; +import type { Id } from "./Id"; import type { State } from "./State"; export interface GameData { diff --git a/src/lib/GameEvent.ts b/src/lib/GameEvent.ts index 6f7d63d..8f5fea4 100644 --- a/src/lib/GameEvent.ts +++ b/src/lib/GameEvent.ts @@ -100,11 +100,7 @@ export class SeatPlayers implements GameEvent { * RollForFirst takes a player index and a value which represents the pips on the die * that the player rolled. It represents and attempt to roll the highest die and go * first. - * - * Players can roll in any order. Each time a player rolls, the event checks to see if - * everyone has rolled and if there is a winner, it sets that person as the first active - * player and ends the rollling-for-first stage of the game. If there was a tie, those - * players are expected to re-roll as a tie breaker. Re-rolling continues until someone + *axpected to re-roll as a tie breaker. Re-rolling continues until someone * wins. */ export class RollForFirst implements GameEvent { @@ -391,10 +387,7 @@ export class Score implements GameEvent { } function scorePips(count: number, pips: number) { - if (count < 3) { - // If not a three of a kind, return the raw dice value... - return pipScore(pips) * count; - } + if (coa // ...otherwise, this is a three or more of a kind. if (pips === 1) { diff --git a/src/lib/Id.ts b/src/lib/Id.ts new file mode 100644 index 0000000..3209e1f --- /dev/null +++ b/src/lib/Id.ts @@ -0,0 +1,11 @@ +import { ObjectId } from "mongodb"; + +export type Id = ObjectId; + +export function createId(): Id { + return new ObjectId(); +} + +export function isId(target: unknown): target is Id { + return target instanceof ObjectId; +} diff --git a/src/lib/Listing.ts b/src/lib/Listing.ts index 6edbc88..c19d755 100644 --- a/src/lib/Listing.ts +++ b/src/lib/Listing.ts @@ -1,7 +1,8 @@ import { hasProperty } from "$lib/validation"; +import { isId, type Id } from "$lib/Id"; -export interface Listing { - id: string; +export interface Listing { + id: Id; createdAt: string; modifiedAt: string | null; deleted: boolean; @@ -12,7 +13,7 @@ export function isListing( target: unknown, dataGuard?: (target: unknown) => target is T ): target is Listing { - if (!hasProperty(target, "id", "string")) return false; + if (!hasProperty(target, "id", isId)) return false; if (!hasProperty(target, "createdAt", "string")) return false; if (!hasProperty(target, "modifiedAt", "null")) { diff --git a/src/lib/Login.ts b/src/lib/Login.ts new file mode 100644 index 0000000..479a4c0 --- /dev/null +++ b/src/lib/Login.ts @@ -0,0 +1,22 @@ +import { hash } from "bcrypt"; +import { hasProperty, hasOnlyKeys } from "./validation"; +import { BCRYPT_SALT_ROUNDS } from "$env/static/private"; + +const saltRounds = parseInt(BCRYPT_SALT_ROUNDS); + +export interface LoginData { + username: string; + password: string; + role: string; +} + +export function isLoginData(target: unknown): target is LoginData { + if (!hasProperty(target, "username", "string")) return false; + if (!hasProperty(target, "password", "string")) return false; + if (!hasProperty(target, "role", "string")) return false; + return hasOnlyKeys(target, ["username", "password", "role"]); +} + +export async function hashPassword(password: string): Promise { + return await hash(password, saltRounds); +} diff --git a/src/lib/server/Game.ts b/src/lib/server/Game.ts index 535a61d..2a993bb 100644 --- a/src/lib/server/Game.ts +++ b/src/lib/server/Game.ts @@ -1,4 +1,4 @@ -import type { Id } from "./Id"; +import type { Id } from "../Id"; import type { GameData } from "../GameData"; import type { State } from "../State"; diff --git a/src/lib/server/Id.ts b/src/lib/server/Id.ts deleted file mode 100644 index 5dfbae8..0000000 --- a/src/lib/server/Id.ts +++ /dev/null @@ -1 +0,0 @@ -export type Id = string; diff --git a/src/lib/server/cache.ts b/src/lib/server/cache.ts index 80c72fb..0f7a443 100644 --- a/src/lib/server/cache.ts +++ b/src/lib/server/cache.ts @@ -1,4 +1,4 @@ -import type { GameData } from "../GameData"; -import type { Listing } from "./modifyListing"; +import type { GameData } from "$lib/GameData"; +import type { Listing } from "$lib/Listing"; export const games: Listing[] = []; diff --git a/src/lib/server/modifyListing.ts b/src/lib/server/modifyListing.ts index 079bdf1..b707cca 100644 --- a/src/lib/server/modifyListing.ts +++ b/src/lib/server/modifyListing.ts @@ -1,9 +1,9 @@ -import { randomUUID } from "crypto"; import type { Listing } from "$lib/Listing"; +import { createId } from "$lib/Id"; export function createNewListing(data: T): Listing { return { - id: randomUUID(), + id: createId(), createdAt: new Date().toISOString(), modifiedAt: null, deleted: false, diff --git a/src/lib/server/mongo.ts b/src/lib/server/mongo.ts new file mode 100644 index 0000000..933cd62 --- /dev/null +++ b/src/lib/server/mongo.ts @@ -0,0 +1,80 @@ +import type { Listing } from "$lib/Listing"; +import { MongoClient, ObjectId, ServerApiVersion, type Document, type WithId } from "mongodb"; +import type { Id } from "../Id"; + +type ListingFromMongo = Omit & { _id: ObjectId }; + +const uri = `mongodb://127.0.0.1:27017`; +const DATABASE = "ten-thousand"; +let client: MongoClient | null = null; + +async function init() { + const c = new MongoClient(uri, { + serverApi: { + version: ServerApiVersion.v1, + strict: true, + deprecationErrors: true + } + }); + + await c.connect(); + return c; +} + +export async function writeListing(col: string, listing: Listing) { + if (client === null) { + client = await init(); + } + + await client.db(DATABASE).collection(col).insertOne(fixListingForMongo(listing)); +} + +export async function readListingById( + col: string, + id: Id, + dataGuard: (target: unknown) => target is T +) {} + +export async function readListingByQuery( + col: string, + query: object, + dataGuard: (target: unknown) => target is Listing +): Promise | null> { + if (client === null) { + client = await init(); + } + + const res = await client.db(DATABASE).collection(col).findOne(query); + + if (res === null) { + return null; + } + + return fixListingFromMongo(res, dataGuard); +} + +function fixListingForMongo(listing: Listing): ListingFromMongo { + // TODO: These two statements are tricky without any. Perhaps id could be optional, + // but that's kind of stupid because it's not optional on the type I'm + // constructing. Figure out something better here. + const adjusted: any = { _id: listing.id, ...listing }; + delete adjusted.id; + return adjusted; +} + +function fixListingFromMongo( + target: WithId, + dataGuard: (target: unknown) => target is Listing +): Listing { + // TODO: These two statements are tricky without any. Perhaps id could be optional, + // but that's kind of stupid because it's not optional on the type I'm + // constructing. Figure out something better here. + const adjusted: any = { id: target._id, ...target }; + delete adjusted._id; + + if (!dataGuard(adjusted)) { + throw new Error("the returned document does not conform to the provided type"); + } + + return adjusted; +} diff --git a/src/lib/server/requestTools.ts b/src/lib/server/requestTools.ts new file mode 100644 index 0000000..45e6356 --- /dev/null +++ b/src/lib/server/requestTools.ts @@ -0,0 +1,157 @@ +import { JWT_SECRET } from "$env/static/private"; +import type { RequestEvent } from "@sveltejs/kit"; +import jwt from "jsonwebtoken"; +import { routeAuth, type Method, type RouteAuthRule } from "./routeAuth"; + +export type LocalCredentials = + | { kind: "Basic"; payload: { username: string; password: string } } + | { kind: "Bearer"; payload: jwt.JwtPayload | string } + | { kind: "None" }; + +export async function getRequestBody( + req: Request, + validation?: (target: unknown) => target is T +): Promise { + if (req.body === null) { + throw new Error("no body is present on the request"); + } + + const body = await req.json(); + + if (validation && !validation(body)) { + throw new Error("body validation failed"); + } + + return body; +} + +export async function isAuthorized(event: RequestEvent): Promise { + let path = event.url.pathname; + let tokenKind: "Basic" | "Bearer" | "None"; + let tokenRole: string; + let tokenDesc: LocalCredentials; + + const parts = breakupPath(path); + + if (parts[0] !== "api") { + // not concerned about requests made to the frontend server + return { kind: "None" }; + } + + const authHeader = event.request.headers.get("authorization"); + + // get token kind and role from the header + if (!authHeader) { + // This is a stranger: they have no token and they will be assigned the default + // role. + tokenKind = "None"; + tokenRole = "default"; + tokenDesc = { kind: "None" }; + } else { + const [kind, token] = authHeader.split(" "); + + if (kind === "Bearer") { + tokenKind = "Bearer"; + + // The role can be derived from the JWT token. + const payload = await jwt.verify(token, JWT_SECRET); + if (typeof payload === "string") { + // I do not assign, and don't know what to do with, these kinds of + // tokens. Perhaps an error should be logged here, since this is a + // weird thing to have stumbled on. + return null; + // user should have a bearer token + } + + tokenRole = payload.role; + tokenDesc = { kind: "Bearer", payload }; + + if (!tokenRole) { + // Something has gone wrong: I should not have issued a token without a + // role. + return null; + } + } else if (kind === "Basic") { + const decoded = Buffer.from(token, "base64").toString("ascii"); + const [username, password] = decoded.split(":"); + + tokenKind = "Basic"; + tokenRole = "default"; + + tokenDesc = { kind: "Basic", payload: { username, password } }; + } else { + // Something the server doesn't recognize was passed as the token kind. + return null; + } + } + + // Determine if the role has the ability to hit this endpoint with this method + // and the given token. + const rules = routeAuth[tokenRole]; + + let hasMatchingAllow = false; + for (const rule of rules) { + console.log("checking rule", rule); + if (matchesRequest(tokenKind, parts, event.request.method as Method, rule)) { + console.log("match!"); + if (rule.action === "deny") { + // if a request matches any deny rule, then it is denied, regardless + // of whether or not it also matches an allow rule. + return null; + } else if (rule.action === "allow") { + hasMatchingAllow = true; + } + } + } + + if (hasMatchingAllow) { + return tokenDesc; + } + return null; +} + +function breakupPath(path: string): string[] { + // remove a leading / + if (path[0] === "/") path = path.slice(1); + + return path.split("/"); +} + +function matchesRequest( + requestTokenKind: "Basic" | "Bearer" | "None", + requestParts: string[], + method: Method, + { endpoint, methods, tokenKind = "Bearer" }: RouteAuthRule +): boolean { + if (tokenKind !== requestTokenKind) { + console.log("token types didn't match", tokenKind, requestTokenKind); + return false; + } + + if (!methods.includes("*") && !methods.includes(method)) { + console.log("Bad method", method); + return false; + } + + const ruleParts = breakupPath(endpoint); + for (let i = 0; i < ruleParts.length; i++) { + const rulePart = ruleParts[i]; + const reqPart = requestParts[i]; + if (rulePart[0] === "[" && rulePart[rulePart.length - 1] === "]") { + // This part of the path represents an ID. + continue; + } + + if (i == ruleParts.length - 1 && rulePart === "*") { + // Rule has a wildcard, anything after it automatically matches. + return true; + } + + if (rulePart !== reqPart) { + console.log("rule parts do not match", rulePart, reqPart); + return false; + } + } + + return true; +} diff --git a/src/lib/server/responseBodies.ts b/src/lib/server/responseBodies.ts index dff2ce3..3a038a5 100644 --- a/src/lib/server/responseBodies.ts +++ b/src/lib/server/responseBodies.ts @@ -17,3 +17,11 @@ export function notFoundResponse() { export function serverErrorResponse() { return Response.json({ error: "Unexpected Server Error" }, { status: 500 }); } + +export function unauthorizedResponse() { + return Response.json({ error: "Unauthorized" }, { status: 401 }); +} + +export function forbiddenResponse() { + return Response.json({ error: "Forbidden" }, { status: 403 }); +} diff --git a/src/lib/server/routeAuth.ts b/src/lib/server/routeAuth.ts new file mode 100644 index 0000000..6c28fa0 --- /dev/null +++ b/src/lib/server/routeAuth.ts @@ -0,0 +1,25 @@ +export type Method = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "*"; + +export interface RouteAuthRule { + action: "allow" | "deny"; + methods: Method[]; + endpoint: string; + tokenKind?: "None" | "Bearer" | "Basic"; // defaults to "Bearer" +} + +export const routeAuth: { [k: string]: RouteAuthRule[] } = { + // default is an unknown user. They are allowed to create a new user for themselves + // without any token, and they are allowed to access the token endpoint to login with + // a Basic token. Other than that, they cannot do anything! + default: [ + { action: "allow", methods: ["POST"], endpoint: "/api/users", tokenKind: "None" }, + { action: "allow", methods: ["POST"], endpoint: "/api/token", tokenKind: "Basic" } + ], + + // player is anyone else. They are authorized to hit any endpoint, using any method, + // with a Bearer token. + player: [ + { action: "allow", methods: ["*"], endpoint: "*" }, + { action: "deny", methods: ["POST"], endpoint: "/api/token" } + ] +}; diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 30f96e3..7e56e31 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -10,11 +10,11 @@ // hasProperty can also receive a specific array type which resembles defining arrays of // items in TypeScript: e.g. string[] is an array of string, (string|number)[] is an array of // strings or numbers. It does not recognize the Array syntax. -export function hasProperty( +export function hasProperty( target: unknown, propertyName: string, - propertyType: string | T, - isNullable: boolean = false, + propertyType: string | ((target: unknown) => boolean), + isNullable: boolean = false ): boolean { if (target === null || target === undefined) return false; @@ -24,14 +24,14 @@ export function hasProperty( return false; } + if (typeof propertyType === "function") { + return propertyType(p); + } + if (p === null) { return propertyType === "null" || isNullable; } - if (typeof propertyType !== "string") { - return p instanceof propertyType - } - if (propertyType === "array") { return Array.isArray(p); } diff --git a/src/routes/api/games/+server.ts b/src/routes/api/games/+server.ts index df3a48c..82a8af6 100644 --- a/src/routes/api/games/+server.ts +++ b/src/routes/api/games/+server.ts @@ -3,13 +3,16 @@ import { listResponse, singleResponse } from "$lib/server/responseBodies"; import { createNewListing } from "$lib/server/modifyListing"; import { Game } from "$lib/server/Game"; import { games } from "$lib/server/cache"; +import { writeListing } from "$lib/server/mongo"; export const GET: RequestHandler = (): Response => { return listResponse(games); }; -export const POST: RequestHandler = (): Response => { +export const POST: RequestHandler = async (): Promise => { const newListing = createNewListing(new Game()); + + await writeListing("games", newListing); games.push(newListing); return singleResponse(newListing.id); diff --git a/src/routes/api/games/[gameid]/+server.ts b/src/routes/api/games/[gameid]/+server.ts index d3c86a1..436ea74 100644 --- a/src/routes/api/games/[gameid]/+server.ts +++ b/src/routes/api/games/[gameid]/+server.ts @@ -1,10 +1,15 @@ import { games } from "$lib/server/cache"; -import { notFoundResponse, singleResponse } from "$lib/server/responseBodies"; +import { badRequestResponse, notFoundResponse, singleResponse } from "$lib/server/responseBodies"; import type { RequestHandler } from "@sveltejs/kit"; export const GET: RequestHandler = ({ params }): Response => { const id = params["gameid"]; - const game = games.find(({ id: gid }) => id === gid); + + if (!id) { + return badRequestResponse("missing gameid parameter"); + } + + const game = games.find(({ id: gid }) => id === gid.toString()); if (!game) { return notFoundResponse(); diff --git a/src/routes/api/token/+server.ts b/src/routes/api/token/+server.ts new file mode 100644 index 0000000..0eb3f1f --- /dev/null +++ b/src/routes/api/token/+server.ts @@ -0,0 +1,53 @@ +import { isListing } from "$lib/Listing"; +import { isLoginData } from "$lib/Login"; +import { readListingByQuery } from "$lib/server/mongo"; +import { getRequestBody } from "$lib/server/requestTools"; +import { + badRequestResponse, + notFoundResponse, + singleResponse, + unauthorizedResponse +} from "$lib/server/responseBodies"; +import type { RequestHandler } from "@sveltejs/kit"; +import { JWT_SECRET } from "$env/static/private"; +import { compare } from "bcrypt"; +import jwt from "jsonwebtoken"; + +export const POST: RequestHandler = async ({ locals }): Promise => { + try { + const { user } = locals; + + if (user.kind !== "Basic") { + return unauthorizedResponse(); + } + + const { username, password } = user.payload; + const listing = await readListingByQuery( + "logins", + { + "data.username": username + }, + (target) => isListing(target, isLoginData) + ); + + if (!listing) { + return notFoundResponse(); + } + + if (await compare(password, listing.data.password)) { + const token = await jwt.sign( + { sub: listing.id, username, role: listing.data.role }, + JWT_SECRET, + { + expiresIn: "1d" + } + ); + + return singleResponse(token); + } + + return badRequestResponse("wrong password"); + } catch (err) { + return badRequestResponse("username and password are required"); + } +}; diff --git a/src/routes/api/users/+server.ts b/src/routes/api/users/+server.ts new file mode 100644 index 0000000..96f4e20 --- /dev/null +++ b/src/routes/api/users/+server.ts @@ -0,0 +1,40 @@ +import { hashPassword, isLoginData } from "$lib/Login"; +import { createNewListing } from "$lib/server/modifyListing"; +import { writeListing } from "$lib/server/mongo"; +import { + badRequestResponse, + forbiddenResponse, + serverErrorResponse, + singleResponse +} from "$lib/server/responseBodies"; +import type { RequestHandler } from "@sveltejs/kit"; + +export const POST: RequestHandler = async ({ request }): Promise => { + let body: unknown; + + console.log("here"); + try { + body = await request.json(); + } catch (err) { + console.log(err); + return badRequestResponse("body is required"); + } + + if (!isLoginData(body)) { + return badRequestResponse("body should contain username and password"); + } + + if (body.role !== "player") { + return forbiddenResponse(); + } + + body.password = await hashPassword(body.password); + const listing = createNewListing(body); + + try { + await writeListing("logins", listing); + return singleResponse(listing.id); + } catch (err) { + return serverErrorResponse(); + } +}; diff --git a/tests/requests.http b/tests/requests.http index 6f8222e..28f019a 100644 --- a/tests/requests.http +++ b/tests/requests.http @@ -1,24 +1,28 @@ -GET http://localhost:5173/api +@token=token + +GET https://localhost:5173/api Accept: application/json ### -GET http://localhost:5173/api/games +GET https://localhost:5173/api/games +Accept: application/json +Authorization: Bearer {{token}} + +### + +POST https://localhost:5173/api/games +Accept: application/json +Authorization: Bearer {{token}} + +### + +GET https://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3 Accept: application/json ### -POST http://localhost:5173/api/games -Accept: application/json - -### - -GET http://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3 -Accept: application/json - -### - -PUT http://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3 +PUT https://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3 Accept: application/json Content-Type: application/json @@ -30,12 +34,32 @@ Content-Type: application/json ### -POST http://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3/turns +POST https://localhost:5173/api/games/de4cdb8c-0346-4ac6-a7a8-b4135b2d79e3/turns Accept: application/json Content-Type: application/json +Authorization: Bearer {{token}} { "kind": "Roll", "player": 2, "value": 4 } + +### + +POST https://localhost:5173/api/users +Accept: application/json +Content-Type: application/json + +{ + "username": "worf", + "password": "klingon", + "role": "player" +} + +### + +POST https://localhost:5173/api/token +Accept: application/json +Content-Type: application/json +Authorization: Basic worf:klingon