Fix auth hook so it returns forbidden if the user is authenticated but not allowed to hit an endpoint.
This commit is contained in:
		@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"useTabs": true,
 | 
						"useTabs": true,
 | 
				
			||||||
	"trailingComma": "none",
 | 
						"trailingComma": "all",
 | 
				
			||||||
	"printWidth": 100,
 | 
						"printWidth": 90,
 | 
				
			||||||
	"plugins": ["prettier-plugin-svelte"],
 | 
						"plugins": ["prettier-plugin-svelte"],
 | 
				
			||||||
	"overrides": [
 | 
						"overrides": [
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										563
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										563
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -21,6 +21,7 @@
 | 
				
			|||||||
				"@types/bcrypt": "^5.0.2",
 | 
									"@types/bcrypt": "^5.0.2",
 | 
				
			||||||
				"@types/jsonwebtoken": "^9.0.7",
 | 
									"@types/jsonwebtoken": "^9.0.7",
 | 
				
			||||||
				"@types/node": "^22.10.7",
 | 
									"@types/node": "^22.10.7",
 | 
				
			||||||
 | 
									"@vitest/coverage-v8": "^2.1.8",
 | 
				
			||||||
				"eslint": "^9.7.0",
 | 
									"eslint": "^9.7.0",
 | 
				
			||||||
				"eslint-config-prettier": "^9.1.0",
 | 
									"eslint-config-prettier": "^9.1.0",
 | 
				
			||||||
				"eslint-plugin-svelte": "^2.36.0",
 | 
									"eslint-plugin-svelte": "^2.36.0",
 | 
				
			||||||
@ -49,6 +50,63 @@
 | 
				
			|||||||
				"node": ">=6.0.0"
 | 
									"node": ">=6.0.0"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@babel/helper-string-parser": {
 | 
				
			||||||
 | 
								"version": "7.25.9",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=6.9.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@babel/helper-validator-identifier": {
 | 
				
			||||||
 | 
								"version": "7.25.9",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=6.9.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@babel/parser": {
 | 
				
			||||||
 | 
								"version": "7.26.7",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@babel/types": "^7.26.7"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"bin": {
 | 
				
			||||||
 | 
									"parser": "bin/babel-parser.js"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=6.0.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@babel/types": {
 | 
				
			||||||
 | 
								"version": "7.26.7",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@babel/helper-string-parser": "^7.25.9",
 | 
				
			||||||
 | 
									"@babel/helper-validator-identifier": "^7.25.9"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=6.9.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@bcoe/v8-coverage": {
 | 
				
			||||||
 | 
								"version": "0.2.3",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/@esbuild/aix-ppc64": {
 | 
							"node_modules/@esbuild/aix-ppc64": {
 | 
				
			||||||
			"version": "0.21.5",
 | 
								"version": "0.21.5",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
 | 
								"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
 | 
				
			||||||
@ -665,6 +723,88 @@
 | 
				
			|||||||
				"url": "https://github.com/sponsors/nzakas"
 | 
									"url": "https://github.com/sponsors/nzakas"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@isaacs/cliui": {
 | 
				
			||||||
 | 
								"version": "8.0.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"string-width": "^5.1.2",
 | 
				
			||||||
 | 
									"string-width-cjs": "npm:string-width@^4.2.0",
 | 
				
			||||||
 | 
									"strip-ansi": "^7.0.1",
 | 
				
			||||||
 | 
									"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
 | 
				
			||||||
 | 
									"wrap-ansi": "^8.1.0",
 | 
				
			||||||
 | 
									"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
 | 
				
			||||||
 | 
								"version": "6.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/ansi-regex?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
 | 
				
			||||||
 | 
								"version": "9.2.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@isaacs/cliui/node_modules/string-width": {
 | 
				
			||||||
 | 
								"version": "5.1.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"eastasianwidth": "^0.2.0",
 | 
				
			||||||
 | 
									"emoji-regex": "^9.2.2",
 | 
				
			||||||
 | 
									"strip-ansi": "^7.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
 | 
				
			||||||
 | 
								"version": "7.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"ansi-regex": "^6.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/strip-ansi?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@istanbuljs/schema": {
 | 
				
			||||||
 | 
								"version": "0.1.3",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=8"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/@jridgewell/gen-mapping": {
 | 
							"node_modules/@jridgewell/gen-mapping": {
 | 
				
			||||||
			"version": "0.3.8",
 | 
								"version": "0.3.8",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
 | 
								"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
 | 
				
			||||||
@ -785,6 +925,17 @@
 | 
				
			|||||||
				"node": ">= 8"
 | 
									"node": ">= 8"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@pkgjs/parseargs": {
 | 
				
			||||||
 | 
								"version": "0.11.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"optional": true,
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=14"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/@polka/url": {
 | 
							"node_modules/@polka/url": {
 | 
				
			||||||
			"version": "1.0.0-next.28",
 | 
								"version": "1.0.0-next.28",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
 | 
								"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
 | 
				
			||||||
@ -1416,6 +1567,39 @@
 | 
				
			|||||||
				"url": "https://opencollective.com/typescript-eslint"
 | 
									"url": "https://opencollective.com/typescript-eslint"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/@vitest/coverage-v8": {
 | 
				
			||||||
 | 
								"version": "2.1.8",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@ampproject/remapping": "^2.3.0",
 | 
				
			||||||
 | 
									"@bcoe/v8-coverage": "^0.2.3",
 | 
				
			||||||
 | 
									"debug": "^4.3.7",
 | 
				
			||||||
 | 
									"istanbul-lib-coverage": "^3.2.2",
 | 
				
			||||||
 | 
									"istanbul-lib-report": "^3.0.1",
 | 
				
			||||||
 | 
									"istanbul-lib-source-maps": "^5.0.6",
 | 
				
			||||||
 | 
									"istanbul-reports": "^3.1.7",
 | 
				
			||||||
 | 
									"magic-string": "^0.30.12",
 | 
				
			||||||
 | 
									"magicast": "^0.3.5",
 | 
				
			||||||
 | 
									"std-env": "^3.8.0",
 | 
				
			||||||
 | 
									"test-exclude": "^7.0.1",
 | 
				
			||||||
 | 
									"tinyrainbow": "^1.2.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://opencollective.com/vitest"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"peerDependencies": {
 | 
				
			||||||
 | 
									"@vitest/browser": "2.1.8",
 | 
				
			||||||
 | 
									"vitest": "2.1.8"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"peerDependenciesMeta": {
 | 
				
			||||||
 | 
									"@vitest/browser": {
 | 
				
			||||||
 | 
										"optional": true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/@vitest/expect": {
 | 
							"node_modules/@vitest/expect": {
 | 
				
			||||||
			"version": "2.1.8",
 | 
								"version": "2.1.8",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
 | 
								"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
 | 
				
			||||||
@ -1981,6 +2165,13 @@
 | 
				
			|||||||
			"dev": true,
 | 
								"dev": true,
 | 
				
			||||||
			"license": "MIT"
 | 
								"license": "MIT"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/eastasianwidth": {
 | 
				
			||||||
 | 
								"version": "0.2.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/ecdsa-sig-formatter": {
 | 
							"node_modules/ecdsa-sig-formatter": {
 | 
				
			||||||
			"version": "1.0.11",
 | 
								"version": "1.0.11",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
 | 
								"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
 | 
				
			||||||
@ -2450,6 +2641,36 @@
 | 
				
			|||||||
			"dev": true,
 | 
								"dev": true,
 | 
				
			||||||
			"license": "ISC"
 | 
								"license": "ISC"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/foreground-child": {
 | 
				
			||||||
 | 
								"version": "3.3.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"cross-spawn": "^7.0.0",
 | 
				
			||||||
 | 
									"signal-exit": "^4.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=14"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/foreground-child/node_modules/signal-exit": {
 | 
				
			||||||
 | 
								"version": "4.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=14"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/fs-minipass": {
 | 
							"node_modules/fs-minipass": {
 | 
				
			||||||
			"version": "2.1.0",
 | 
								"version": "2.1.0",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
 | 
								"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
 | 
				
			||||||
@ -2600,6 +2821,13 @@
 | 
				
			|||||||
			"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
 | 
								"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
 | 
				
			||||||
			"license": "ISC"
 | 
								"license": "ISC"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/html-escaper": {
 | 
				
			||||||
 | 
								"version": "2.0.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/https-proxy-agent": {
 | 
							"node_modules/https-proxy-agent": {
 | 
				
			||||||
			"version": "5.0.1",
 | 
								"version": "5.0.1",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
 | 
								"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
 | 
				
			||||||
@ -2737,6 +2965,92 @@
 | 
				
			|||||||
			"dev": true,
 | 
								"dev": true,
 | 
				
			||||||
			"license": "ISC"
 | 
								"license": "ISC"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/istanbul-lib-coverage": {
 | 
				
			||||||
 | 
								"version": "3.2.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BSD-3-Clause",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=8"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/istanbul-lib-report": {
 | 
				
			||||||
 | 
								"version": "3.0.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BSD-3-Clause",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"istanbul-lib-coverage": "^3.0.0",
 | 
				
			||||||
 | 
									"make-dir": "^4.0.0",
 | 
				
			||||||
 | 
									"supports-color": "^7.1.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=10"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/istanbul-lib-report/node_modules/make-dir": {
 | 
				
			||||||
 | 
								"version": "4.0.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"semver": "^7.5.3"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=10"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/istanbul-lib-source-maps": {
 | 
				
			||||||
 | 
								"version": "5.0.6",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BSD-3-Clause",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@jridgewell/trace-mapping": "^0.3.23",
 | 
				
			||||||
 | 
									"debug": "^4.1.1",
 | 
				
			||||||
 | 
									"istanbul-lib-coverage": "^3.0.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=10"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/istanbul-reports": {
 | 
				
			||||||
 | 
								"version": "3.1.7",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BSD-3-Clause",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"html-escaper": "^2.0.0",
 | 
				
			||||||
 | 
									"istanbul-lib-report": "^3.0.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=8"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/jackspeak": {
 | 
				
			||||||
 | 
								"version": "3.4.3",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BlueOak-1.0.0",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@isaacs/cliui": "^8.0.2"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"optionalDependencies": {
 | 
				
			||||||
 | 
									"@pkgjs/parseargs": "^0.11.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/js-yaml": {
 | 
							"node_modules/js-yaml": {
 | 
				
			||||||
			"version": "4.1.0",
 | 
								"version": "4.1.0",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
 | 
								"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
 | 
				
			||||||
@ -2944,6 +3258,13 @@
 | 
				
			|||||||
			"dev": true,
 | 
								"dev": true,
 | 
				
			||||||
			"license": "MIT"
 | 
								"license": "MIT"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/lru-cache": {
 | 
				
			||||||
 | 
								"version": "10.4.3",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/magic-string": {
 | 
							"node_modules/magic-string": {
 | 
				
			||||||
			"version": "0.30.17",
 | 
								"version": "0.30.17",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
 | 
								"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
 | 
				
			||||||
@ -2954,6 +3275,18 @@
 | 
				
			|||||||
				"@jridgewell/sourcemap-codec": "^1.5.0"
 | 
									"@jridgewell/sourcemap-codec": "^1.5.0"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/magicast": {
 | 
				
			||||||
 | 
								"version": "0.3.5",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@babel/parser": "^7.25.4",
 | 
				
			||||||
 | 
									"@babel/types": "^7.25.4",
 | 
				
			||||||
 | 
									"source-map-js": "^1.2.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/make-dir": {
 | 
							"node_modules/make-dir": {
 | 
				
			||||||
			"version": "3.1.0",
 | 
								"version": "3.1.0",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
 | 
								"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
 | 
				
			||||||
@ -3331,6 +3664,13 @@
 | 
				
			|||||||
				"url": "https://github.com/sponsors/sindresorhus"
 | 
									"url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/package-json-from-dist": {
 | 
				
			||||||
 | 
								"version": "1.0.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BlueOak-1.0.0"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/parent-module": {
 | 
							"node_modules/parent-module": {
 | 
				
			||||||
			"version": "1.0.1",
 | 
								"version": "1.0.1",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 | 
								"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 | 
				
			||||||
@ -3373,6 +3713,23 @@
 | 
				
			|||||||
				"node": ">=8"
 | 
									"node": ">=8"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/path-scurry": {
 | 
				
			||||||
 | 
								"version": "1.11.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "BlueOak-1.0.0",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"lru-cache": "^10.2.0",
 | 
				
			||||||
 | 
									"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=16 || 14 >=14.18"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/pathe": {
 | 
							"node_modules/pathe": {
 | 
				
			||||||
			"version": "1.1.2",
 | 
								"version": "1.1.2",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
 | 
								"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
 | 
				
			||||||
@ -3889,6 +4246,22 @@
 | 
				
			|||||||
				"node": ">=8"
 | 
									"node": ">=8"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/string-width-cjs": {
 | 
				
			||||||
 | 
								"name": "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==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"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": {
 | 
							"node_modules/strip-ansi": {
 | 
				
			||||||
			"version": "6.0.1",
 | 
								"version": "6.0.1",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
								"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
				
			||||||
@ -3901,6 +4274,20 @@
 | 
				
			|||||||
				"node": ">=8"
 | 
									"node": ">=8"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/strip-ansi-cjs": {
 | 
				
			||||||
 | 
								"name": "strip-ansi",
 | 
				
			||||||
 | 
								"version": "6.0.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"ansi-regex": "^5.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=8"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/strip-json-comments": {
 | 
							"node_modules/strip-json-comments": {
 | 
				
			||||||
			"version": "3.1.1",
 | 
								"version": "3.1.1",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 | 
								"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 | 
				
			||||||
@ -4070,6 +4457,78 @@
 | 
				
			|||||||
				"node": ">=10"
 | 
									"node": ">=10"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/test-exclude": {
 | 
				
			||||||
 | 
								"version": "7.0.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"@istanbuljs/schema": "^0.1.2",
 | 
				
			||||||
 | 
									"glob": "^10.4.1",
 | 
				
			||||||
 | 
									"minimatch": "^9.0.4"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=18"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/test-exclude/node_modules/brace-expansion": {
 | 
				
			||||||
 | 
								"version": "2.0.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"balanced-match": "^1.0.0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/test-exclude/node_modules/glob": {
 | 
				
			||||||
 | 
								"version": "10.4.5",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"foreground-child": "^3.1.0",
 | 
				
			||||||
 | 
									"jackspeak": "^3.1.2",
 | 
				
			||||||
 | 
									"minimatch": "^9.0.4",
 | 
				
			||||||
 | 
									"minipass": "^7.1.2",
 | 
				
			||||||
 | 
									"package-json-from-dist": "^1.0.0",
 | 
				
			||||||
 | 
									"path-scurry": "^1.11.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"bin": {
 | 
				
			||||||
 | 
									"glob": "dist/esm/bin.mjs"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/test-exclude/node_modules/minimatch": {
 | 
				
			||||||
 | 
								"version": "9.0.5",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"brace-expansion": "^2.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=16 || 14 >=14.17"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/isaacs"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/test-exclude/node_modules/minipass": {
 | 
				
			||||||
 | 
								"version": "7.1.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "ISC",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=16 || 14 >=14.17"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/tiny-glob": {
 | 
							"node_modules/tiny-glob": {
 | 
				
			||||||
			"version": "0.2.9",
 | 
								"version": "0.2.9",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
 | 
								"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
 | 
				
			||||||
@ -4488,6 +4947,110 @@
 | 
				
			|||||||
				"node": ">=0.10.0"
 | 
									"node": ">=0.10.0"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi": {
 | 
				
			||||||
 | 
								"version": "8.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"ansi-styles": "^6.1.0",
 | 
				
			||||||
 | 
									"string-width": "^5.0.1",
 | 
				
			||||||
 | 
									"strip-ansi": "^7.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi-cjs": {
 | 
				
			||||||
 | 
								"name": "wrap-ansi",
 | 
				
			||||||
 | 
								"version": "7.0.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"ansi-styles": "^4.0.0",
 | 
				
			||||||
 | 
									"string-width": "^4.1.0",
 | 
				
			||||||
 | 
									"strip-ansi": "^6.0.0"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=10"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi/node_modules/ansi-regex": {
 | 
				
			||||||
 | 
								"version": "6.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/ansi-regex?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi/node_modules/ansi-styles": {
 | 
				
			||||||
 | 
								"version": "6.2.1",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi/node_modules/emoji-regex": {
 | 
				
			||||||
 | 
								"version": "9.2.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi/node_modules/string-width": {
 | 
				
			||||||
 | 
								"version": "5.1.2",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"eastasianwidth": "^0.2.0",
 | 
				
			||||||
 | 
									"emoji-regex": "^9.2.2",
 | 
				
			||||||
 | 
									"strip-ansi": "^7.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"node_modules/wrap-ansi/node_modules/strip-ansi": {
 | 
				
			||||||
 | 
								"version": "7.1.0",
 | 
				
			||||||
 | 
								"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
 | 
				
			||||||
 | 
								"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
 | 
				
			||||||
 | 
								"dev": true,
 | 
				
			||||||
 | 
								"license": "MIT",
 | 
				
			||||||
 | 
								"dependencies": {
 | 
				
			||||||
 | 
									"ansi-regex": "^6.0.1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"engines": {
 | 
				
			||||||
 | 
									"node": ">=12"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"funding": {
 | 
				
			||||||
 | 
									"url": "https://github.com/chalk/strip-ansi?sponsor=1"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"node_modules/wrappy": {
 | 
							"node_modules/wrappy": {
 | 
				
			||||||
			"version": "1.0.2",
 | 
								"version": "1.0.2",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 | 
								"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@
 | 
				
			|||||||
		"@types/bcrypt": "^5.0.2",
 | 
							"@types/bcrypt": "^5.0.2",
 | 
				
			||||||
		"@types/jsonwebtoken": "^9.0.7",
 | 
							"@types/jsonwebtoken": "^9.0.7",
 | 
				
			||||||
		"@types/node": "^22.10.7",
 | 
							"@types/node": "^22.10.7",
 | 
				
			||||||
 | 
							"@vitest/coverage-v8": "^2.1.8",
 | 
				
			||||||
		"eslint": "^9.7.0",
 | 
							"eslint": "^9.7.0",
 | 
				
			||||||
		"eslint-config-prettier": "^9.1.0",
 | 
							"eslint-config-prettier": "^9.1.0",
 | 
				
			||||||
		"eslint-plugin-svelte": "^2.36.0",
 | 
							"eslint-plugin-svelte": "^2.36.0",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							@ -1,6 +1,6 @@
 | 
				
			|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
 | 
					// See https://svelte.dev/docs/kit/types#app.d.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { LocalCredentials } from "$lib/server/requestTools";
 | 
					import type { LocalCredentials } from "$lib/server/getRequestBody";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// for information about these interfaces
 | 
					// for information about these interfaces
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +0,0 @@
 | 
				
			|||||||
import { describe, it, expect } from 'vitest';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('sum test', () => {
 | 
					 | 
				
			||||||
	it('adds 1 + 2 to equal 3', () => {
 | 
					 | 
				
			||||||
		expect(1 + 2).toBe(3);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@ -1,15 +1,29 @@
 | 
				
			|||||||
import { isAuthorized } from "$lib/server/requestTools";
 | 
					import * as auth from "$lib/server/auth";
 | 
				
			||||||
import { unauthorizedResponse } from "$lib/server/responseBodies";
 | 
					import { forbiddenResponse, unauthorizedResponse } from "$lib/server/responseBodies";
 | 
				
			||||||
import type { Handle } from "@sveltejs/kit";
 | 
					import { routeAuth, type Method } from "$lib/server/routeAuth";
 | 
				
			||||||
 | 
					import { type Handle } from "@sveltejs/kit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const handle: Handle = async ({ event, resolve }) => {
 | 
					export const handle: Handle = async ({ event, resolve }) => {
 | 
				
			||||||
	const auth = await isAuthorized(event);
 | 
						const creds = await auth.authenticate(event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!auth) {
 | 
						if (!creds) {
 | 
				
			||||||
		return unauthorizedResponse();
 | 
							return unauthorizedResponse();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	event.locals.user = auth;
 | 
						const authResult = auth.isAuthorized(
 | 
				
			||||||
 | 
							routeAuth,
 | 
				
			||||||
 | 
							event.request.method as Method,
 | 
				
			||||||
 | 
							event.url.pathname,
 | 
				
			||||||
 | 
							creds,
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (authResult === auth.AuthorizationResult.Denied) {
 | 
				
			||||||
 | 
							return forbiddenResponse();
 | 
				
			||||||
 | 
						} else if (authResult === auth.AuthorizationResult.Unauthenticated) {
 | 
				
			||||||
 | 
							return unauthorizedResponse();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event.locals.user = creds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return await resolve(event);
 | 
						return await resolve(event);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { hasOnlyKeys, hasProperty } from "./validation";
 | 
					import { hasOnlyKeys, hasProperty } from "./validation";
 | 
				
			||||||
import type { Id } from "./Id";
 | 
					import { isId, type Id } from "./Id";
 | 
				
			||||||
import type { State } from "./State";
 | 
					import type { State } from "./State";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GameData {
 | 
					export interface GameData {
 | 
				
			||||||
@ -13,7 +13,15 @@ export function isGameData(target: unknown): target is GameData {
 | 
				
			|||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!hasProperty(target, "players", "string[]")) {
 | 
						if ("players" in (target as any)) {
 | 
				
			||||||
 | 
							const { players } = target as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const player of players) {
 | 
				
			||||||
 | 
								if (!isId(player)) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -174,9 +174,7 @@ export class RollForFirst implements GameEvent {
 | 
				
			|||||||
				state.dieCount = 6;
 | 
									state.dieCount = 6;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				// ...otherwise, setup for tie breaking rolls.
 | 
									// ...otherwise, setup for tie breaking rolls.
 | 
				
			||||||
				state.scores = scores.map((_, i) =>
 | 
									state.scores = scores.map((_, i) => (ties.has(i) ? FIRST_ROLL_PENDING : FIRST_ROLL_LOST));
 | 
				
			||||||
					ties.has(i) ? FIRST_ROLL_PENDING : FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -368,9 +366,7 @@ export class Score implements GameEvent {
 | 
				
			|||||||
		// Increment the index of the active player, circling back to 1 if the player
 | 
							// Increment the index of the active player, circling back to 1 if the player
 | 
				
			||||||
		// who just scored was the last player in the array.
 | 
							// who just scored was the last player in the array.
 | 
				
			||||||
		state.playing =
 | 
							state.playing =
 | 
				
			||||||
			playerCount - 1 === this.player
 | 
								playerCount - 1 === this.player ? (state.playing = 0) : (state.playing = this.player + 1);
 | 
				
			||||||
				? (state.playing = 0)
 | 
					 | 
				
			||||||
				: (state.playing = this.player + 1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		state.dieCount = 6;
 | 
							state.dieCount = 6;
 | 
				
			||||||
		delete state.heldScore;
 | 
							delete state.heldScore;
 | 
				
			||||||
@ -387,7 +383,10 @@ export class Score implements GameEvent {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function scorePips(count: number, pips: number) {
 | 
					function scorePips(count: number, pips: number) {
 | 
				
			||||||
	if (coa
 | 
						if (count < 3) {
 | 
				
			||||||
 | 
							// If not a three of a kind, return the raw dice value...
 | 
				
			||||||
 | 
							return pipScore(pips) * count;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ...otherwise, this is a three or more of a kind.
 | 
						// ...otherwise, this is a three or more of a kind.
 | 
				
			||||||
	if (pips === 1) {
 | 
						if (pips === 1) {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,14 @@ export function createId(): Id {
 | 
				
			|||||||
	return new ObjectId();
 | 
						return new ObjectId();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function idFromString(str: string) {
 | 
				
			||||||
 | 
						return new ObjectId(str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function stringFromId(id: Id) {
 | 
				
			||||||
 | 
						return id.toString();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isId(target: unknown): target is Id {
 | 
					export function isId(target: unknown): target is Id {
 | 
				
			||||||
	return target instanceof ObjectId;
 | 
						return target instanceof ObjectId;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,35 @@
 | 
				
			|||||||
import { JWT_SECRET } from "$env/static/private";
 | 
					import { JWT_SECRET } from "$env/static/private";
 | 
				
			||||||
import type { RequestEvent } from "@sveltejs/kit";
 | 
					import type { RequestEvent } from "@sveltejs/kit";
 | 
				
			||||||
import jwt from "jsonwebtoken";
 | 
					import jwt from "jsonwebtoken";
 | 
				
			||||||
import { routeAuth, type Method, type RouteAuthRule } from "./routeAuth";
 | 
					import { type Method, type RouteAuthRule } from "./routeAuth";
 | 
				
			||||||
 | 
					import type { Listing } from "$lib/Listing";
 | 
				
			||||||
 | 
					import type { LoginData } from "$lib/Login";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type LocalCredentials =
 | 
					export type LocalCredentials = (
 | 
				
			||||||
	| { kind: "Basic"; payload: { username: string; password: string } }
 | 
						| { kind: "Basic"; payload: { username: string; password: string } }
 | 
				
			||||||
	| { kind: "Bearer"; payload: jwt.JwtPayload | string }
 | 
						| { kind: "Bearer"; payload: jwt.JwtPayload | string }
 | 
				
			||||||
	| { kind: "None" };
 | 
						| { kind: "None" }
 | 
				
			||||||
 | 
					) & { role: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getRequestBody<T = unknown>(
 | 
					export enum AuthorizationResult {
 | 
				
			||||||
	req: Request,
 | 
						Allowed,
 | 
				
			||||||
	validation?: (target: unknown) => target is T
 | 
						Denied,
 | 
				
			||||||
): Promise<T> {
 | 
						Unauthenticated,
 | 
				
			||||||
	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<LocalCredentials | null> {
 | 
					export async function createToken(listing: Listing<LoginData>) {
 | 
				
			||||||
 | 
						return await jwt.sign(
 | 
				
			||||||
 | 
							{ sub: listing.id, username: listing.data.username, role: listing.data.role },
 | 
				
			||||||
 | 
							JWT_SECRET,
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								expiresIn: "1d",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function authenticate(
 | 
				
			||||||
 | 
						event: RequestEvent,
 | 
				
			||||||
 | 
					): Promise<LocalCredentials | null> {
 | 
				
			||||||
	let path = event.url.pathname;
 | 
						let path = event.url.pathname;
 | 
				
			||||||
	let tokenKind: "Basic" | "Bearer" | "None";
 | 
						let tokenKind: "Basic" | "Bearer" | "None";
 | 
				
			||||||
	let tokenRole: string;
 | 
						let tokenRole: string;
 | 
				
			||||||
@ -35,7 +39,7 @@ export async function isAuthorized(event: RequestEvent): Promise<LocalCredential
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if (parts[0] !== "api") {
 | 
						if (parts[0] !== "api") {
 | 
				
			||||||
		// not concerned about requests made to the frontend server
 | 
							// not concerned about requests made to the frontend server
 | 
				
			||||||
		return { kind: "None" };
 | 
							return { kind: "None", role: "default" };
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const authHeader = event.request.headers.get("authorization");
 | 
						const authHeader = event.request.headers.get("authorization");
 | 
				
			||||||
@ -46,7 +50,7 @@ export async function isAuthorized(event: RequestEvent): Promise<LocalCredential
 | 
				
			|||||||
		// role.
 | 
							// role.
 | 
				
			||||||
		tokenKind = "None";
 | 
							tokenKind = "None";
 | 
				
			||||||
		tokenRole = "default";
 | 
							tokenRole = "default";
 | 
				
			||||||
		tokenDesc = { kind: "None" };
 | 
							tokenDesc = { kind: "None", role: tokenRole };
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		const [kind, token] = authHeader.split(" ");
 | 
							const [kind, token] = authHeader.split(" ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,7 +68,7 @@ export async function isAuthorized(event: RequestEvent): Promise<LocalCredential
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tokenRole = payload.role;
 | 
								tokenRole = payload.role;
 | 
				
			||||||
			tokenDesc = { kind: "Bearer", payload };
 | 
								tokenDesc = { kind: "Bearer", payload, role: tokenRole };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (!tokenRole) {
 | 
								if (!tokenRole) {
 | 
				
			||||||
				// Something has gone wrong: I should not have issued a token without a
 | 
									// Something has gone wrong: I should not have issued a token without a
 | 
				
			||||||
@ -78,36 +82,46 @@ export async function isAuthorized(event: RequestEvent): Promise<LocalCredential
 | 
				
			|||||||
			tokenKind = "Basic";
 | 
								tokenKind = "Basic";
 | 
				
			||||||
			tokenRole = "default";
 | 
								tokenRole = "default";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tokenDesc = { kind: "Basic", payload: { username, password } };
 | 
								tokenDesc = { kind: "Basic", payload: { username, password }, role: tokenRole };
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			// Something the server doesn't recognize was passed as the token kind.
 | 
								// Something the server doesn't recognize was passed as the token kind.
 | 
				
			||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tokenDesc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isAuthorized(
 | 
				
			||||||
 | 
						roleRules: { [k: string]: RouteAuthRule[] },
 | 
				
			||||||
 | 
						method: Method,
 | 
				
			||||||
 | 
						path: string,
 | 
				
			||||||
 | 
						creds: LocalCredentials,
 | 
				
			||||||
 | 
					): AuthorizationResult {
 | 
				
			||||||
	// Determine if the role has the ability to hit this endpoint with this method
 | 
						// Determine if the role has the ability to hit this endpoint with this method
 | 
				
			||||||
	// and the given token.
 | 
						// and the given token.
 | 
				
			||||||
	const rules = routeAuth[tokenRole];
 | 
						const parts = breakupPath(path);
 | 
				
			||||||
 | 
						const { role: tokenRole, kind: tokenKind } = creds;
 | 
				
			||||||
 | 
						const rules = roleRules[tokenRole];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let hasMatchingAllow = false;
 | 
						let hasMatchingAllow = false;
 | 
				
			||||||
	for (const rule of rules) {
 | 
						for (const rule of rules) {
 | 
				
			||||||
		console.log("checking rule", rule);
 | 
							if (matchesRequest(parts, method, rule)) {
 | 
				
			||||||
		if (matchesRequest(tokenKind, parts, event.request.method as Method, rule)) {
 | 
								if (tokenKind !== (rule.tokenKind ?? "Bearer")) {
 | 
				
			||||||
			console.log("match!");
 | 
									return AuthorizationResult.Unauthenticated;
 | 
				
			||||||
			if (rule.action === "deny") {
 | 
								} else if (rule.action === "deny") {
 | 
				
			||||||
				// if a request matches any deny rule, then it is denied, regardless
 | 
									// if a request matches any deny rule, then it is denied, regardless
 | 
				
			||||||
				// of whether or not it also matches an allow rule.
 | 
									// of whether or not it also matches an allow rule.
 | 
				
			||||||
				return null;
 | 
									return AuthorizationResult.Denied;
 | 
				
			||||||
			} else if (rule.action === "allow") {
 | 
								} else if (rule.action === "allow") {
 | 
				
			||||||
				hasMatchingAllow = true;
 | 
									hasMatchingAllow = true;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (hasMatchingAllow) {
 | 
						if (hasMatchingAllow) return AuthorizationResult.Allowed;
 | 
				
			||||||
		return tokenDesc;
 | 
						if (tokenKind === "Bearer") return AuthorizationResult.Denied;
 | 
				
			||||||
	}
 | 
						else return AuthorizationResult.Unauthenticated;
 | 
				
			||||||
	return null;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function breakupPath(path: string): string[] {
 | 
					function breakupPath(path: string): string[] {
 | 
				
			||||||
@ -118,18 +132,11 @@ function breakupPath(path: string): string[] {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function matchesRequest(
 | 
					function matchesRequest(
 | 
				
			||||||
	requestTokenKind: "Basic" | "Bearer" | "None",
 | 
					 | 
				
			||||||
	requestParts: string[],
 | 
						requestParts: string[],
 | 
				
			||||||
	method: Method,
 | 
						method: Method,
 | 
				
			||||||
	{ endpoint, methods, tokenKind = "Bearer" }: RouteAuthRule
 | 
						{ endpoint, methods }: RouteAuthRule,
 | 
				
			||||||
): boolean {
 | 
					): boolean {
 | 
				
			||||||
	if (tokenKind !== requestTokenKind) {
 | 
					 | 
				
			||||||
		console.log("token types didn't match", tokenKind, requestTokenKind);
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!methods.includes("*") && !methods.includes(method)) {
 | 
						if (!methods.includes("*") && !methods.includes(method)) {
 | 
				
			||||||
		console.log("Bad method", method);
 | 
					 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -148,7 +155,6 @@ function matchesRequest(
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (rulePart !== reqPart) {
 | 
							if (rulePart !== reqPart) {
 | 
				
			||||||
			console.log("rule parts do not match", rulePart, reqPart);
 | 
					 | 
				
			||||||
			return false;
 | 
								return false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/lib/server/getRequestBody.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/lib/server/getRequestBody.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					export async function getRequestBody<T = unknown>(
 | 
				
			||||||
 | 
						req: Request,
 | 
				
			||||||
 | 
						validation?: (target: unknown) => target is T,
 | 
				
			||||||
 | 
					): Promise<T> {
 | 
				
			||||||
 | 
						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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -13,13 +13,13 @@ export const routeAuth: { [k: string]: RouteAuthRule[] } = {
 | 
				
			|||||||
	// a Basic token. Other than that, they cannot do anything!
 | 
						// a Basic token. Other than that, they cannot do anything!
 | 
				
			||||||
	default: [
 | 
						default: [
 | 
				
			||||||
		{ action: "allow", methods: ["POST"], endpoint: "/api/users", tokenKind: "None" },
 | 
							{ action: "allow", methods: ["POST"], endpoint: "/api/users", tokenKind: "None" },
 | 
				
			||||||
		{ action: "allow", methods: ["POST"], endpoint: "/api/token", tokenKind: "Basic" }
 | 
							{ action: "allow", methods: ["POST"], endpoint: "/api/token", tokenKind: "Basic" },
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// player is anyone else. They are authorized to hit any endpoint, using any method,
 | 
						// player is anyone else. They are authorized to hit any endpoint, using any method,
 | 
				
			||||||
	// with a Bearer token.
 | 
						// with a Bearer token.
 | 
				
			||||||
	player: [
 | 
						player: [
 | 
				
			||||||
		{ action: "allow", methods: ["*"], endpoint: "*" },
 | 
							{ action: "allow", methods: ["*"], endpoint: "*" },
 | 
				
			||||||
		{ action: "deny", methods: ["POST"], endpoint: "/api/token" }
 | 
							{ action: "deny", methods: ["POST"], endpoint: "/api/token" },
 | 
				
			||||||
	]
 | 
						],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,19 @@
 | 
				
			|||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import { Game } from "../../Game";
 | 
					import { Game } from "$lib/server/Game";
 | 
				
			||||||
import { deepEqual, ok, throws } from "node:assert/strict";
 | 
					import { deepEqual, ok, throws } from "node:assert/strict";
 | 
				
			||||||
 | 
					import { createId, idFromString, stringFromId } from "$lib/Id";
 | 
				
			||||||
 | 
					import { equal } from "node:assert";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Game", () => {
 | 
					describe("Game", () => {
 | 
				
			||||||
 | 
						const idString = stringFromId(createId());
 | 
				
			||||||
	describe("addPlayer", () => {
 | 
						describe("addPlayer", () => {
 | 
				
			||||||
		it("should push a player id into the player array", () => {
 | 
							it("should push a player id into the player array", () => {
 | 
				
			||||||
			const game = new Game();
 | 
								const game = new Game();
 | 
				
			||||||
			deepEqual(game.players, []);
 | 
								deepEqual(game.players, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			game.addPlayer("some-id");
 | 
								game.addPlayer(idFromString(idString));
 | 
				
			||||||
			deepEqual(game.players, ["some-id"]);
 | 
								equal(game.players.length, 1);
 | 
				
			||||||
 | 
								equal(stringFromId(game.players[0]), idString);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,26 +1,29 @@
 | 
				
			|||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import { GameData, isGameData } from "../../GameData";
 | 
					import { type GameData, isGameData } from "$lib/GameData";
 | 
				
			||||||
import { equal, ok } from "node:assert/strict";
 | 
					import { equal, ok } from "node:assert/strict";
 | 
				
			||||||
 | 
					import { createId, idFromString, stringFromId } from "$lib/Id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("GameData", () => {
 | 
					describe("GameData", () => {
 | 
				
			||||||
 | 
						const idString = stringFromId(createId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	describe("isGameData", () => {
 | 
						describe("isGameData", () => {
 | 
				
			||||||
		it("rejects a malformed object", () => {
 | 
							it("rejects a malformed object", () => {
 | 
				
			||||||
			let data: unknown = {
 | 
								let data: unknown = {
 | 
				
			||||||
				players: ["id", 3],
 | 
									players: [idFromString(idString), idString],
 | 
				
			||||||
				isStarted: false,
 | 
									isStarted: false,
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			equal(isGameData(data), false);
 | 
								equal(isGameData(data), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			data = {
 | 
								data = {
 | 
				
			||||||
				players: ["id"],
 | 
									players: [idFromString(idString)],
 | 
				
			||||||
				isStarted: null,
 | 
									isStarted: null,
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			equal(isGameData(data), false);
 | 
								equal(isGameData(data), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			data = {
 | 
								data = {
 | 
				
			||||||
				players: ["id"],
 | 
									players: [idFromString(idString)],
 | 
				
			||||||
				isStarted: false,
 | 
									isStarted: false,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			equal(isGameData(data), false);
 | 
								equal(isGameData(data), false);
 | 
				
			||||||
@ -28,7 +31,7 @@ describe("GameData", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("rejects an object with extra properties", () => {
 | 
							it("rejects an object with extra properties", () => {
 | 
				
			||||||
			const data: GameData & { extra: boolean } = {
 | 
								const data: GameData & { extra: boolean } = {
 | 
				
			||||||
				players: ["id"],
 | 
									players: [idFromString(idString)],
 | 
				
			||||||
				isStarted: false,
 | 
									isStarted: false,
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
				extra: true,
 | 
									extra: true,
 | 
				
			||||||
@ -39,7 +42,7 @@ describe("GameData", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should accept a proper GameData object", () => {
 | 
							it("should accept a proper GameData object", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				players: ["id"],
 | 
									players: [idFromString(idString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
				isStarted: false,
 | 
									isStarted: false,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
@ -12,19 +12,14 @@ import {
 | 
				
			|||||||
} from "../../GameEvent";
 | 
					} from "../../GameEvent";
 | 
				
			||||||
import type { GameEventData } from "../../GameEvent";
 | 
					import type { GameEventData } from "../../GameEvent";
 | 
				
			||||||
import type { GameData } from "../../GameData";
 | 
					import type { GameData } from "../../GameData";
 | 
				
			||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import type { State } from "../../State";
 | 
					import type { State } from "../../State";
 | 
				
			||||||
import { doesNotThrow, deepStrictEqual, equal, ok, throws } from "assert";
 | 
					import { doesNotThrow, deepStrictEqual, equal, ok, throws } from "assert";
 | 
				
			||||||
 | 
					import { createId, idFromString, stringFromId } from "$lib/Id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Game Events", () => {
 | 
					describe("Game Events", () => {
 | 
				
			||||||
	describe("isGameEventData", () => {
 | 
						describe("isGameEventData", () => {
 | 
				
			||||||
		it("should return false if the target is not an object", () => {
 | 
							it("should return false if the target is not an object", () => {
 | 
				
			||||||
			// const target = {
 | 
					 | 
				
			||||||
			// 	kind: GameEventKind.Hold,
 | 
					 | 
				
			||||||
			// 	player: 0,
 | 
					 | 
				
			||||||
			// 	value: [1, 4],
 | 
					 | 
				
			||||||
			// };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			equal(isGameEventData("target"), false);
 | 
								equal(isGameEventData("target"), false);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -60,10 +55,13 @@ describe("Game Events", () => {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	describe("getGameEvent", () => {
 | 
						describe("getGameEvent", () => {
 | 
				
			||||||
 | 
							const idString = stringFromId(createId());
 | 
				
			||||||
 | 
							const anotherIdString = stringFromId(createId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		it("should throw if the kind is unkown", () => {
 | 
							it("should throw if the kind is unkown", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: false,
 | 
									isStarted: false,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,7 +77,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
		it("should throw when SeatPlayers has the wrong number of players", () => {
 | 
							it("should throw when SeatPlayers has the wrong number of players", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: true,
 | 
									isStarted: true,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -94,7 +92,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
		it("should return a SeatPlayers object when the number of players is correct", () => {
 | 
							it("should return a SeatPlayers object when the number of players is correct", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: true,
 | 
									isStarted: true,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -109,7 +107,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
		it("should throw an error if the player passes a full roll with Roll", () => {
 | 
							it("should throw an error if the player passes a full roll with Roll", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: true,
 | 
									isStarted: true,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,7 +123,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
		it("should return a Roll object with dice values when the player passes a die count as a value", () => {
 | 
							it("should return a Roll object with dice values when the player passes a die count as a value", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: true,
 | 
									isStarted: true,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,7 +143,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
		it("should return the class that corresponds with a given kind", () => {
 | 
							it("should return the class that corresponds with a given kind", () => {
 | 
				
			||||||
			const data: GameData = {
 | 
								const data: GameData = {
 | 
				
			||||||
				isStarted: true,
 | 
									isStarted: true,
 | 
				
			||||||
				players: ["42", "1,"],
 | 
									players: [idFromString(idString), idFromString(anotherIdString)],
 | 
				
			||||||
				state: {},
 | 
									state: {},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -222,10 +220,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			it("should throw if the value is not a number", () => {
 | 
								it("should throw if the value is not a number", () => {
 | 
				
			||||||
				throws(
 | 
									throws(() => new RollForFirst({ kind: GameEventKind.RollForFirst, player: 0, value: [4] }));
 | 
				
			||||||
					() =>
 | 
					 | 
				
			||||||
						new RollForFirst({ kind: GameEventKind.RollForFirst, player: 0, value: [4] }),
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -267,7 +262,6 @@ describe("Game Events", () => {
 | 
				
			|||||||
				const state: State = { scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING] };
 | 
									const state: State = { scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				throws(() => ev.run(state));
 | 
									throws(() => ev.run(state));
 | 
				
			||||||
				console.log("done");
 | 
					 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			it("should throw if the player has already rolled", () => {
 | 
								it("should throw if the player has already rolled", () => {
 | 
				
			||||||
@ -296,12 +290,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			it("should reset the scores and set the winning player when everyone has rolled", () => {
 | 
								it("should reset the scores and set the winning player when everyone has rolled", () => {
 | 
				
			||||||
				const state: State = {
 | 
									const state: State = {
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				let ev = new RollForFirst({
 | 
									let ev = new RollForFirst({
 | 
				
			||||||
@ -324,24 +313,14 @@ describe("Game Events", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				deepStrictEqual(state, {
 | 
									deepStrictEqual(state, {
 | 
				
			||||||
					dieCount: 6,
 | 
										dieCount: 6,
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
					playing: 2,
 | 
										playing: 2,
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			it("should reset tied players for tie breaker", () => {
 | 
								it("should reset tied players for tie breaker", () => {
 | 
				
			||||||
				const state: State = {
 | 
									const state: State = {
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				let ev = new RollForFirst({
 | 
									let ev = new RollForFirst({
 | 
				
			||||||
@ -361,23 +340,13 @@ describe("Game Events", () => {
 | 
				
			|||||||
				ev.run(state);
 | 
									ev.run(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				deepStrictEqual(state, {
 | 
									deepStrictEqual(state, {
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_LOST, FIRST_ROLL_LOST, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
						FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			it("should throw if a player whose lost tries to roll again", () => {
 | 
								it("should throw if a player whose lost tries to roll again", () => {
 | 
				
			||||||
				const state: State = {
 | 
									const state: State = {
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_LOST, FIRST_ROLL_LOST, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
						FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const ev = new RollForFirst({
 | 
									const ev = new RollForFirst({
 | 
				
			||||||
@ -390,12 +359,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			it("should allow tied players to keep rolling until somoene wins", () => {
 | 
								it("should allow tied players to keep rolling until somoene wins", () => {
 | 
				
			||||||
				const state: State = {
 | 
									const state: State = {
 | 
				
			||||||
					scores: [
 | 
										scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_LOST, FIRST_ROLL_PENDING],
 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
						FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
					],
 | 
					 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// simulate another 3-way tie
 | 
									// simulate another 3-way tie
 | 
				
			||||||
@ -415,12 +379,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
				deepStrictEqual(
 | 
									deepStrictEqual(
 | 
				
			||||||
					state,
 | 
										state,
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						scores: [
 | 
											scores: [FIRST_ROLL_PENDING, FIRST_ROLL_PENDING, FIRST_ROLL_LOST, FIRST_ROLL_PENDING],
 | 
				
			||||||
							FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
							FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
							FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
							FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						],
 | 
					 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					"shouldn't change in a 3-way tie",
 | 
										"shouldn't change in a 3-way tie",
 | 
				
			||||||
				);
 | 
									);
 | 
				
			||||||
@ -438,12 +397,7 @@ describe("Game Events", () => {
 | 
				
			|||||||
				deepStrictEqual(
 | 
									deepStrictEqual(
 | 
				
			||||||
					state,
 | 
										state,
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						scores: [
 | 
											scores: [FIRST_ROLL_PENDING, FIRST_ROLL_LOST, FIRST_ROLL_LOST, FIRST_ROLL_PENDING],
 | 
				
			||||||
							FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
							FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
							FIRST_ROLL_LOST,
 | 
					 | 
				
			||||||
							FIRST_ROLL_PENDING,
 | 
					 | 
				
			||||||
						],
 | 
					 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					"should update for a smaller tie",
 | 
										"should update for a smaller tie",
 | 
				
			||||||
				);
 | 
									);
 | 
				
			||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import { createNewListing, updateListing } from "../modifyListing";
 | 
					import { createNewListing, updateListing } from "../modifyListing";
 | 
				
			||||||
import { Game } from "../../Game";
 | 
					import { Game } from "$lib/server/Game";
 | 
				
			||||||
import { deepEqual, equal, ok } from "node:assert/strict";
 | 
					import { deepEqual, equal, ok } from "node:assert/strict";
 | 
				
			||||||
 | 
					import { isId } from "$lib/Id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Listing", () => {
 | 
					describe("Listing", () => {
 | 
				
			||||||
	describe("createNewListing", () => {
 | 
						describe("createNewListing", () => {
 | 
				
			||||||
@ -10,10 +11,10 @@ describe("Listing", () => {
 | 
				
			|||||||
			const listing = createNewListing(game);
 | 
								const listing = createNewListing(game);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ok(listing.data instanceof Game);
 | 
								ok(listing.data instanceof Game);
 | 
				
			||||||
			ok(listing.createdAt instanceof Date);
 | 
					 | 
				
			||||||
			ok(listing.modifiedAt === null);
 | 
								ok(listing.modifiedAt === null);
 | 
				
			||||||
			ok(!listing.deleted);
 | 
								ok(!listing.deleted);
 | 
				
			||||||
			equal(typeof listing.id, "string");
 | 
								ok(isId(listing.id));
 | 
				
			||||||
 | 
								equal(typeof listing.createdAt, "string");
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -27,7 +28,7 @@ describe("Listing", () => {
 | 
				
			|||||||
			const updatedListing = updateListing(listing, update);
 | 
								const updatedListing = updateListing(listing, update);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			deepEqual(updatedListing.data, update);
 | 
								deepEqual(updatedListing.data, update);
 | 
				
			||||||
			ok(updatedListing.modifiedAt instanceof Date);
 | 
								equal(typeof updatedListing.modifiedAt, "string");
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import { getDiceRoll } from "../../getDiceRoll";
 | 
					import { getDiceRoll } from "$lib/server/getDiceRoll";
 | 
				
			||||||
import { deepEqual } from "node:assert/strict";
 | 
					import { deepEqual } from "node:assert/strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function testRandom() {
 | 
					function testRandom() {
 | 
				
			||||||
@ -11,5 +11,8 @@ describe("getDiceRoll", () => {
 | 
				
			|||||||
	it("should return an array of numbers from 1 to 6 with a given length", () => {
 | 
						it("should return an array of numbers from 1 to 6 with a given length", () => {
 | 
				
			||||||
		let rand = getDiceRoll(6, testRandom());
 | 
							let rand = getDiceRoll(6, testRandom());
 | 
				
			||||||
		deepEqual(rand, [0, 1, 3, 3, 4, 6]);
 | 
							deepEqual(rand, [0, 1, 3, 3, 4, 6]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rand = getDiceRoll(3, testRandom());
 | 
				
			||||||
 | 
							deepEqual(rand, [0, 1, 3]);
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { describe, it } from "node:test";
 | 
					import { describe, it } from "vitest";
 | 
				
			||||||
import { equal, ok } from "node:assert/strict";
 | 
					import { equal, ok } from "node:assert/strict";
 | 
				
			||||||
import { hasProperty, hasOnlyKeys } from "../../validation";
 | 
					import { hasProperty, hasOnlyKeys } from "../../validation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -46,7 +46,7 @@ describe("validation", () => {
 | 
				
			|||||||
				third: false,
 | 
									third: false,
 | 
				
			||||||
				fourth: null,
 | 
									fourth: null,
 | 
				
			||||||
				fifth: { something: "important" },
 | 
									fifth: { something: "important" },
 | 
				
			||||||
				sixth: ["one", "two"],
 | 
									sixth: ["one", "two"]
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ok(hasProperty(target, "first", "string"));
 | 
								ok(hasProperty(target, "first", "string"));
 | 
				
			||||||
@ -59,7 +59,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return false if passed an array type and the property isn't an array", () => {
 | 
							it("should return false if passed an array type and the property isn't an array", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				arr: "not array",
 | 
									arr: "not array"
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			equal(hasProperty(target, "arr", "string[]"), false);
 | 
								equal(hasProperty(target, "arr", "string[]"), false);
 | 
				
			||||||
@ -67,7 +67,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return false if the defined array contains a non-matching element", () => {
 | 
							it("should return false if the defined array contains a non-matching element", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				arr: ["I", "was", "born", "in", 1989],
 | 
									arr: ["I", "was", "born", "in", 1989]
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			equal(hasProperty(target, "arr", "string[]"), false);
 | 
								equal(hasProperty(target, "arr", "string[]"), false);
 | 
				
			||||||
@ -75,7 +75,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return true if all the elements in a defined array match", () => {
 | 
							it("should return true if all the elements in a defined array match", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				arr: ["I", "was", "born", "in", "1989"],
 | 
									arr: ["I", "was", "born", "in", "1989"]
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ok(hasProperty(target, "arr", "string[]"));
 | 
								ok(hasProperty(target, "arr", "string[]"));
 | 
				
			||||||
@ -83,7 +83,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return true if all the elements in a defined array match one of multiple types", () => {
 | 
							it("should return true if all the elements in a defined array match one of multiple types", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				arr: ["I", "was", "born", "in", 1989],
 | 
									arr: ["I", "was", "born", "in", 1989]
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ok(hasProperty(target, "arr", "(string|number)[]"));
 | 
								ok(hasProperty(target, "arr", "(string|number)[]"));
 | 
				
			||||||
@ -91,7 +91,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return true if type is null but property is nullable", () => {
 | 
							it("should return true if type is null but property is nullable", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				nullable: null,
 | 
									nullable: null
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ok(hasProperty(target, "nullable", "string", true));
 | 
								ok(hasProperty(target, "nullable", "string", true));
 | 
				
			||||||
@ -107,7 +107,7 @@ describe("validation", () => {
 | 
				
			|||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				one: "one",
 | 
									one: "one",
 | 
				
			||||||
				two: "two",
 | 
									two: "two",
 | 
				
			||||||
				three: "three",
 | 
									three: "three"
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const keys = ["one", "two"];
 | 
								const keys = ["one", "two"];
 | 
				
			||||||
@ -119,7 +119,7 @@ describe("validation", () => {
 | 
				
			|||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				one: "one",
 | 
									one: "one",
 | 
				
			||||||
				two: "two",
 | 
									two: "two",
 | 
				
			||||||
				three: "three",
 | 
									three: "three"
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const keys = ["one", "two", "three"];
 | 
								const keys = ["one", "two", "three"];
 | 
				
			||||||
@ -129,7 +129,7 @@ describe("validation", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		it("should return true if the target has only a subset of the provided keys", () => {
 | 
							it("should return true if the target has only a subset of the provided keys", () => {
 | 
				
			||||||
			const target = {
 | 
								const target = {
 | 
				
			||||||
				one: "one",
 | 
									one: "one"
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const keys = ["one", "two", "three"];
 | 
								const keys = ["one", "two", "three"];
 | 
				
			||||||
@ -1,17 +1,15 @@
 | 
				
			|||||||
import { isListing } from "$lib/Listing";
 | 
					import { isListing } from "$lib/Listing";
 | 
				
			||||||
import { isLoginData } from "$lib/Login";
 | 
					import { isLoginData } from "$lib/Login";
 | 
				
			||||||
import { readListingByQuery } from "$lib/server/mongo";
 | 
					import { readListingByQuery } from "$lib/server/mongo";
 | 
				
			||||||
import { getRequestBody } from "$lib/server/requestTools";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	badRequestResponse,
 | 
						badRequestResponse,
 | 
				
			||||||
	notFoundResponse,
 | 
						notFoundResponse,
 | 
				
			||||||
	singleResponse,
 | 
						singleResponse,
 | 
				
			||||||
	unauthorizedResponse
 | 
						unauthorizedResponse,
 | 
				
			||||||
} from "$lib/server/responseBodies";
 | 
					} from "$lib/server/responseBodies";
 | 
				
			||||||
import type { RequestHandler } from "@sveltejs/kit";
 | 
					import type { RequestHandler } from "@sveltejs/kit";
 | 
				
			||||||
import { JWT_SECRET } from "$env/static/private";
 | 
					 | 
				
			||||||
import { compare } from "bcrypt";
 | 
					import { compare } from "bcrypt";
 | 
				
			||||||
import jwt from "jsonwebtoken";
 | 
					import { createToken } from "$lib/server/auth";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const POST: RequestHandler = async ({ locals }): Promise<Response> => {
 | 
					export const POST: RequestHandler = async ({ locals }): Promise<Response> => {
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
@ -25,9 +23,9 @@ export const POST: RequestHandler = async ({ locals }): Promise<Response> => {
 | 
				
			|||||||
		const listing = await readListingByQuery(
 | 
							const listing = await readListingByQuery(
 | 
				
			||||||
			"logins",
 | 
								"logins",
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				"data.username": username
 | 
									"data.username": username,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			(target) => isListing(target, isLoginData)
 | 
								(target) => isListing(target, isLoginData),
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!listing) {
 | 
							if (!listing) {
 | 
				
			||||||
@ -35,14 +33,7 @@ export const POST: RequestHandler = async ({ locals }): Promise<Response> => {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (await compare(password, listing.data.password)) {
 | 
							if (await compare(password, listing.data.password)) {
 | 
				
			||||||
			const token = await jwt.sign(
 | 
								const token = await createToken(listing);
 | 
				
			||||||
				{ sub: listing.id, username, role: listing.data.role },
 | 
					 | 
				
			||||||
				JWT_SECRET,
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					expiresIn: "1d"
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return singleResponse(token);
 | 
								return singleResponse(token);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,18 +5,16 @@ import {
 | 
				
			|||||||
	badRequestResponse,
 | 
						badRequestResponse,
 | 
				
			||||||
	forbiddenResponse,
 | 
						forbiddenResponse,
 | 
				
			||||||
	serverErrorResponse,
 | 
						serverErrorResponse,
 | 
				
			||||||
	singleResponse
 | 
						singleResponse,
 | 
				
			||||||
} from "$lib/server/responseBodies";
 | 
					} from "$lib/server/responseBodies";
 | 
				
			||||||
import type { RequestHandler } from "@sveltejs/kit";
 | 
					import type { RequestHandler } from "@sveltejs/kit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const POST: RequestHandler = async ({ request }): Promise<Response> => {
 | 
					export const POST: RequestHandler = async ({ request }): Promise<Response> => {
 | 
				
			||||||
	let body: unknown;
 | 
						let body: unknown;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	console.log("here");
 | 
					 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		body = await request.json();
 | 
							body = await request.json();
 | 
				
			||||||
	} catch (err) {
 | 
						} catch (err) {
 | 
				
			||||||
		console.log(err);
 | 
					 | 
				
			||||||
		return badRequestResponse("body is required");
 | 
							return badRequestResponse("body is required");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										83
									
								
								src/tests/hooks.server.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/tests/hooks.server.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import type { Cookies, RequestEvent } from "@sveltejs/kit";
 | 
				
			||||||
 | 
					import { describe, it, expect, afterEach } from "vitest";
 | 
				
			||||||
 | 
					import * as auth from "../lib/server/auth";
 | 
				
			||||||
 | 
					import { handle } from "../hooks.server";
 | 
				
			||||||
 | 
					import { createId } from "$lib/Id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let events: RequestEvent[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mock RequestEvent data that can be passed into the handle function. It doesn't matter
 | 
				
			||||||
 | 
					// that most of these fields are incoherent, the only things that will be tested here are
 | 
				
			||||||
 | 
					// that this event is passed to the isAuthorized function, and that the locas are
 | 
				
			||||||
 | 
					// populated.
 | 
				
			||||||
 | 
					const event: RequestEvent = {
 | 
				
			||||||
 | 
						cookies: {} as Cookies,
 | 
				
			||||||
 | 
						fetch: async (): Promise<Response> => {
 | 
				
			||||||
 | 
							return new Response();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						getClientAddress: () => "",
 | 
				
			||||||
 | 
						locals: { user: {} },
 | 
				
			||||||
 | 
						params: {},
 | 
				
			||||||
 | 
						platform: undefined,
 | 
				
			||||||
 | 
						request: new Request(new URL("https://localhost/api")),
 | 
				
			||||||
 | 
						route: { id: "" },
 | 
				
			||||||
 | 
						setHeaders: () => {},
 | 
				
			||||||
 | 
						url: new URL("https://localhost/api"),
 | 
				
			||||||
 | 
						isDataRequest: false,
 | 
				
			||||||
 | 
						isSubRequest: false,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resolve = async (event: RequestEvent): Promise<Response> => {
 | 
				
			||||||
 | 
						events.push(event);
 | 
				
			||||||
 | 
						return new Response();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("handle", () => {
 | 
				
			||||||
 | 
						afterEach(() => {
 | 
				
			||||||
 | 
							event.locals.user = {};
 | 
				
			||||||
 | 
							event.request.headers.delete("authorization");
 | 
				
			||||||
 | 
							events = [];
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						it("returns unauthorized response if caller isn't properly authenticated", async () => {
 | 
				
			||||||
 | 
							event.request.headers.set("authorization", "Nonesense Token");
 | 
				
			||||||
 | 
							const res = await handle({ event, resolve });
 | 
				
			||||||
 | 
							expect(res.status).to.equal(401);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						it("returns unauthorized response if caller is missing required auth header", async () => {
 | 
				
			||||||
 | 
							const res = await handle({ event, resolve });
 | 
				
			||||||
 | 
							expect(res.status).to.equal(401);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						it("returns forbidden response if caller isn't authorized", async () => {
 | 
				
			||||||
 | 
							// This is a weird scenario, but it does reflect the way I expect this to work.
 | 
				
			||||||
 | 
							// Svelte Kit does not seem to provide me with a tool that I can use to authorize
 | 
				
			||||||
 | 
							// users in one place. I would have to check their role at the start of each
 | 
				
			||||||
 | 
							// endpoint function (yuck). The endpoint below doesn't exist, but it will still
 | 
				
			||||||
 | 
							// return 403 instead of 404 because the auth check happens before the route
 | 
				
			||||||
 | 
							// matching, and the user isn't authorized to hit this nonesense endpoint.
 | 
				
			||||||
 | 
							const ev: RequestEvent = {
 | 
				
			||||||
 | 
								...event,
 | 
				
			||||||
 | 
								url: new URL("https://localhost/api/some/secret/route"),
 | 
				
			||||||
 | 
								request: new Request("https://localhost/api/some/secret/route"),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const token = await auth.createToken({
 | 
				
			||||||
 | 
								id: createId(),
 | 
				
			||||||
 | 
								createdAt: new Date().toString(),
 | 
				
			||||||
 | 
								modifiedAt: new Date().toString(),
 | 
				
			||||||
 | 
								deleted: false,
 | 
				
			||||||
 | 
								data: {
 | 
				
			||||||
 | 
									password: "somethin' secret!",
 | 
				
			||||||
 | 
									username: "Mr. Man",
 | 
				
			||||||
 | 
									role: "default",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ev.request.headers.set("authorization", `Bearer ${token}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const res = await handle({ event: ev, resolve });
 | 
				
			||||||
 | 
							expect(res.status).to.equal(403);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { defineConfig } from "vitest/config";
 | 
					import { configDefaults, coverageConfigDefaults, defineConfig } from "vitest/config";
 | 
				
			||||||
import { sveltekit } from "@sveltejs/kit/vite";
 | 
					import { sveltekit } from "@sveltejs/kit/vite";
 | 
				
			||||||
import { readFileSync } from "fs";
 | 
					import { readFileSync } from "fs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8,12 +8,15 @@ export default defineConfig({
 | 
				
			|||||||
	server: {
 | 
						server: {
 | 
				
			||||||
		https: {
 | 
							https: {
 | 
				
			||||||
			key: readFileSync(`${__dirname}/cert/key.pem`),
 | 
								key: readFileSync(`${__dirname}/cert/key.pem`),
 | 
				
			||||||
			cert: readFileSync(`${__dirname}/cert/cert.pem`)
 | 
								cert: readFileSync(`${__dirname}/cert/cert.pem`),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		proxy: {}
 | 
							proxy: {},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test: {
 | 
						test: {
 | 
				
			||||||
		include: ["src/**/*.{test,spec}.{js,ts}"]
 | 
							include: ["src/**/*.{test,spec}.{js,ts}"],
 | 
				
			||||||
	}
 | 
							coverage: {
 | 
				
			||||||
 | 
								exclude: [...coverageConfigDefaults.exclude, "svelte.config.js"],
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user