mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Refactoring the app layout, fixing checks for session and not user to fix totp.
This commit is contained in:
parent
6e67b2d4e1
commit
0f2344c70f
46 changed files with 748 additions and 724 deletions
12
package.json
12
package.json
|
|
@ -54,7 +54,7 @@
|
|||
"postcss-load-config": "^5.1.0",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.8",
|
||||
"prettier-plugin-svelte": "^3.3.0",
|
||||
"svelte": "5.0.0-next.175",
|
||||
"svelte-check": "^3.8.6",
|
||||
"svelte-headless-table": "^0.18.3",
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
"@iconify-icons/line-md": "^1.2.30",
|
||||
"@iconify-icons/mdi": "^1.2.48",
|
||||
"@inlang/paraglide-sveltekit": "^0.11.1",
|
||||
"@internationalized/date": "^3.5.6",
|
||||
"@internationalized/date": "^3.6.0",
|
||||
"@lucia-auth/adapter-drizzle": "^1.1.0",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@needle-di/core": "^0.8.4",
|
||||
|
|
@ -96,22 +96,22 @@
|
|||
"@oslojs/otp": "^1.0.0",
|
||||
"@oslojs/webauthn": "^1.0.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@scalar/hono-api-reference": "^0.5.159",
|
||||
"@scalar/hono-api-reference": "^0.5.160",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/adapter-vercel": "^5.4.7",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"bullmq": "^5.27.0",
|
||||
"bullmq": "^5.28.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cookie": "^1.0.1",
|
||||
"cookie": "^1.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-expand": "^11.0.7",
|
||||
"drizzle-orm": "^0.36.3",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"handlebars": "^4.7.8",
|
||||
"hono": "^4.6.10",
|
||||
"hono": "^4.6.11",
|
||||
"hono-pino": "^0.3.0",
|
||||
"hono-rate-limiter": "^0.4.0",
|
||||
"hono-zod-openapi": "^0.4.2",
|
||||
|
|
|
|||
128
pnpm-lock.yaml
128
pnpm-lock.yaml
|
|
@ -13,13 +13,13 @@ importers:
|
|||
version: 5.1.0
|
||||
'@hono/swagger-ui':
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(hono@4.6.10)
|
||||
version: 0.4.1(hono@4.6.11)
|
||||
'@hono/zod-openapi':
|
||||
specifier: ^0.15.3
|
||||
version: 0.15.3(hono@4.6.10)(zod@3.23.8)
|
||||
version: 0.15.3(hono@4.6.11)(zod@3.23.8)
|
||||
'@hono/zod-validator':
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2(hono@4.6.10)(zod@3.23.8)
|
||||
version: 0.2.2(hono@4.6.11)(zod@3.23.8)
|
||||
'@iconify-icons/line-md':
|
||||
specifier: ^1.2.30
|
||||
version: 1.2.30
|
||||
|
|
@ -30,8 +30,8 @@ importers:
|
|||
specifier: ^0.11.1
|
||||
version: 0.11.5(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.11(@types/node@20.17.6)))(svelte@5.0.0-next.175)(vite@5.4.11(@types/node@20.17.6)))
|
||||
'@internationalized/date':
|
||||
specifier: ^3.5.6
|
||||
version: 3.5.6
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
'@lucia-auth/adapter-drizzle':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(drizzle-orm@0.36.3(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.1)(postgres@3.4.5))(lucia@3.2.0)
|
||||
|
|
@ -72,8 +72,8 @@ importers:
|
|||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@scalar/hono-api-reference':
|
||||
specifier: ^0.5.159
|
||||
version: 0.5.159(hono@4.6.10)
|
||||
specifier: ^0.5.160
|
||||
version: 0.5.160(hono@4.6.11)
|
||||
'@sveltejs/adapter-node':
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.11(@types/node@20.17.6)))(svelte@5.0.0-next.175)(vite@5.4.11(@types/node@20.17.6)))
|
||||
|
|
@ -87,8 +87,8 @@ importers:
|
|||
specifier: ^1.9.1
|
||||
version: 1.9.1
|
||||
bullmq:
|
||||
specifier: ^5.27.0
|
||||
version: 5.27.0
|
||||
specifier: ^5.28.1
|
||||
version: 5.28.1
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
|
|
@ -96,8 +96,8 @@ importers:
|
|||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
cookie:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
dotenv:
|
||||
specifier: ^16.4.5
|
||||
version: 16.4.5
|
||||
|
|
@ -117,17 +117,17 @@ importers:
|
|||
specifier: ^4.7.8
|
||||
version: 4.7.8
|
||||
hono:
|
||||
specifier: ^4.6.10
|
||||
version: 4.6.10
|
||||
specifier: ^4.6.11
|
||||
version: 4.6.11
|
||||
hono-pino:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(hono@4.6.10)(pino@9.5.0)
|
||||
version: 0.3.0(hono@4.6.11)(pino@9.5.0)
|
||||
hono-rate-limiter:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0(hono@4.6.10)
|
||||
version: 0.4.0(hono@4.6.11)
|
||||
hono-zod-openapi:
|
||||
specifier: ^0.4.2
|
||||
version: 0.4.2(hono@4.6.10)(zod@3.23.8)
|
||||
version: 0.4.2(hono@4.6.11)(zod@3.23.8)
|
||||
html-entities:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
|
|
@ -178,7 +178,7 @@ importers:
|
|||
version: 0.2.2
|
||||
stoker:
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(@asteasolutions/zod-to-openapi@7.1.2(zod@3.23.8))(@hono/zod-openapi@0.15.3(hono@4.6.10)(zod@3.23.8))(hono@4.6.10)(openapi3-ts@4.4.0)
|
||||
version: 1.3.0(@asteasolutions/zod-to-openapi@7.1.2(zod@3.23.8))(@hono/zod-openapi@0.15.3(hono@4.6.11)(zod@3.23.8))(hono@4.6.11)(openapi3-ts@4.4.0)
|
||||
svelte-lazy-loader:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
|
|
@ -292,8 +292,8 @@ importers:
|
|||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
prettier-plugin-svelte:
|
||||
specifier: ^3.2.8
|
||||
version: 3.2.8(prettier@3.3.3)(svelte@5.0.0-next.175)
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0(prettier@3.3.3)(svelte@5.0.0-next.175)
|
||||
svelte:
|
||||
specifier: 5.0.0-next.175
|
||||
version: 5.0.0-next.175
|
||||
|
|
@ -1665,8 +1665,8 @@ packages:
|
|||
'@inlang/translatable@1.3.1':
|
||||
resolution: {integrity: sha512-VAtle21vRpIrB+axtHFrFB0d1HtDaaNj+lV77eZQTJyOWbTFYTVIQJ8WAbyw9eu4F6h6QC2FutLyxjMomxfpcQ==}
|
||||
|
||||
'@internationalized/date@3.5.6':
|
||||
resolution: {integrity: sha512-jLxQjefH9VI5P9UQuqB6qNKnvFt1Ky1TPIzHGsIlCi7sZZoMR8SdYbBGRvM0y+Jtb+ez4ieBzmiAUcpmPYpyOw==}
|
||||
'@internationalized/date@3.6.0':
|
||||
resolution: {integrity: sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==}
|
||||
|
||||
'@ioredis/commands@1.2.0':
|
||||
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||
|
|
@ -2344,8 +2344,8 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@scalar/hono-api-reference@0.5.159':
|
||||
resolution: {integrity: sha512-nUKaN0CKvytbXPj9b6taF/efKKRqEUwhVxlfLVjrJXN0eHNHDWxG9e/5Tyw1o2MXJo1cQpGZ4qTh48k/8u6ZjA==}
|
||||
'@scalar/hono-api-reference@0.5.160':
|
||||
resolution: {integrity: sha512-3NXXh4B+5sa9QEchtQNKH0me4pEB8soFfCSqOxsuB0d8agokDO574cP9Qq4l6vwSh6FMiqSdaGTv5459g8hTCg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
hono: ^4.0.0
|
||||
|
|
@ -2354,8 +2354,8 @@ packages:
|
|||
resolution: {integrity: sha512-6geH9ehvQ/sG/xUyy3e0lyOw3BaY5s6nn22wHjEJhcobdmWyFER0O6m7AU0ZN4QTjle/gYvFJOjj552l/rsNSw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@scalar/types@0.0.19':
|
||||
resolution: {integrity: sha512-wOxtXd35BS0DaVhBopQUB8c8hfLQ+/PKEr99GbOZW+4DWCrEB8JfWJgvpJyxHU6by7LHNVY4fvpFQR7Ezh1IIw==}
|
||||
'@scalar/types@0.0.20':
|
||||
resolution: {integrity: sha512-Sx7tqiuV9ZNp2XpIXKD/srzTjjb4I6aof0Y7+U5MgEuKPwrRnYS/BaocdNgWLnpCZisBIg5qp4YSqozxibabVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sideway/address@4.1.5':
|
||||
|
|
@ -2801,8 +2801,8 @@ packages:
|
|||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
bullmq@5.27.0:
|
||||
resolution: {integrity: sha512-DZWrjDLkecZZ1/43h/SkG6CxU8nO/Lq/0svVoQdw33ksUCGfccgjbvCa/cuxHP/OvhxlTAA0cO3dBOoaT7sRFQ==}
|
||||
bullmq@5.28.1:
|
||||
resolution: {integrity: sha512-iSoqziPLKH//mmoc4Aj3/opTmk1PgFdITwUrx/wDqrTxfBRjnTGInsu129LCEY6d+SmhZWnA9PYU6ciX+NT64A==}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
|
|
@ -2956,8 +2956,8 @@ packages:
|
|||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cookie@1.0.1:
|
||||
resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==}
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
core-js@3.38.1:
|
||||
|
|
@ -3659,8 +3659,8 @@ packages:
|
|||
hono: ^4.6.3
|
||||
zod: ^3.21.4
|
||||
|
||||
hono@4.6.10:
|
||||
resolution: {integrity: sha512-IXXNfRAZEahFnWBhUUlqKEGF9upeE6hZoRZszvNkyAz/CYp+iVbxm3viMvStlagRJohjlBRGOQ7f4jfcV0XMGg==}
|
||||
hono@4.6.11:
|
||||
resolution: {integrity: sha512-f0LwJQFKdUUrCUAVowxSvNCjyzI7ZLt8XWYU/EApyeq5FfOvHFarBaE5rjU9HTNFk4RI0FkdB2edb3p/7xZjzQ==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
hookable@5.5.3:
|
||||
|
|
@ -4648,8 +4648,8 @@ packages:
|
|||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier-plugin-svelte@3.2.8:
|
||||
resolution: {integrity: sha512-PAHmmU5cGZdnhW4mWhmvxuG2PVbbHIxUuPOdUKvfE+d4Qt2d29iU5VWrPdsaW5YqVEE0nqhlvN4eoKmVMpIF3Q==}
|
||||
prettier-plugin-svelte@3.3.0:
|
||||
resolution: {integrity: sha512-iNoYiQUx4zwqbQDW/bk0WR75w+QiY4fHJQpGQ5v8Yr7X5m7YoSvs2buUnhoYFXNAL32ULVmrjPSc0vVOHJsO0Q==}
|
||||
peerDependencies:
|
||||
prettier: ^3.0.0
|
||||
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
|
||||
|
|
@ -6325,25 +6325,25 @@ snapshots:
|
|||
'@hapi/hoek': 9.3.0
|
||||
optional: true
|
||||
|
||||
'@hono/swagger-ui@0.4.1(hono@4.6.10)':
|
||||
'@hono/swagger-ui@0.4.1(hono@4.6.11)':
|
||||
dependencies:
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
|
||||
'@hono/zod-openapi@0.15.3(hono@4.6.10)(zod@3.23.8)':
|
||||
'@hono/zod-openapi@0.15.3(hono@4.6.11)(zod@3.23.8)':
|
||||
dependencies:
|
||||
'@asteasolutions/zod-to-openapi': 7.1.2(zod@3.23.8)
|
||||
'@hono/zod-validator': 0.2.2(hono@4.6.10)(zod@3.23.8)
|
||||
hono: 4.6.10
|
||||
'@hono/zod-validator': 0.2.2(hono@4.6.11)(zod@3.23.8)
|
||||
hono: 4.6.11
|
||||
zod: 3.23.8
|
||||
|
||||
'@hono/zod-validator@0.2.2(hono@4.6.10)(zod@3.23.8)':
|
||||
'@hono/zod-validator@0.2.2(hono@4.6.11)(zod@3.23.8)':
|
||||
dependencies:
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
zod: 3.23.8
|
||||
|
||||
'@hono/zod-validator@0.4.1(hono@4.6.10)(zod@3.23.8)':
|
||||
'@hono/zod-validator@0.4.1(hono@4.6.11)(zod@3.23.8)':
|
||||
dependencies:
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
zod: 3.23.8
|
||||
|
||||
'@humanwhocodes/config-array@0.13.0':
|
||||
|
|
@ -6571,7 +6571,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@inlang/language-tag': 1.5.1
|
||||
|
||||
'@internationalized/date@3.5.6':
|
||||
'@internationalized/date@3.6.0':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.13
|
||||
|
||||
|
|
@ -6665,7 +6665,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@floating-ui/core': 1.6.8
|
||||
'@floating-ui/dom': 1.6.11
|
||||
'@internationalized/date': 3.5.6
|
||||
'@internationalized/date': 3.6.0
|
||||
dequal: 2.0.3
|
||||
focus-trap: 7.6.0
|
||||
nanoid: 5.0.7
|
||||
|
|
@ -6675,7 +6675,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@floating-ui/core': 1.6.8
|
||||
'@floating-ui/dom': 1.6.11
|
||||
'@internationalized/date': 3.5.6
|
||||
'@internationalized/date': 3.6.0
|
||||
dequal: 2.0.3
|
||||
focus-trap: 7.6.0
|
||||
nanoid: 5.0.7
|
||||
|
|
@ -7226,14 +7226,14 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.24.0':
|
||||
optional: true
|
||||
|
||||
'@scalar/hono-api-reference@0.5.159(hono@4.6.10)':
|
||||
'@scalar/hono-api-reference@0.5.160(hono@4.6.11)':
|
||||
dependencies:
|
||||
'@scalar/types': 0.0.19
|
||||
hono: 4.6.10
|
||||
'@scalar/types': 0.0.20
|
||||
hono: 4.6.11
|
||||
|
||||
'@scalar/openapi-types@0.1.5': {}
|
||||
|
||||
'@scalar/types@0.0.19':
|
||||
'@scalar/types@0.0.20':
|
||||
dependencies:
|
||||
'@scalar/openapi-types': 0.1.5
|
||||
'@unhead/schema': 1.11.11
|
||||
|
|
@ -7701,7 +7701,7 @@ snapshots:
|
|||
|
||||
bits-ui@0.21.16(svelte@5.0.0-next.175):
|
||||
dependencies:
|
||||
'@internationalized/date': 3.5.6
|
||||
'@internationalized/date': 3.6.0
|
||||
'@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175)
|
||||
nanoid: 5.0.7
|
||||
svelte: 5.0.0-next.175
|
||||
|
|
@ -7766,7 +7766,7 @@ snapshots:
|
|||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bullmq@5.27.0:
|
||||
bullmq@5.28.1:
|
||||
dependencies:
|
||||
cron-parser: 4.9.0
|
||||
ioredis: 5.4.1
|
||||
|
|
@ -7921,7 +7921,7 @@ snapshots:
|
|||
|
||||
cookie@0.6.0: {}
|
||||
|
||||
cookie@1.0.1: {}
|
||||
cookie@1.0.2: {}
|
||||
|
||||
core-js@3.38.1: {}
|
||||
|
||||
|
|
@ -8636,24 +8636,24 @@ snapshots:
|
|||
|
||||
help-me@5.0.0: {}
|
||||
|
||||
hono-pino@0.3.0(hono@4.6.10)(pino@9.5.0):
|
||||
hono-pino@0.3.0(hono@4.6.11)(pino@9.5.0):
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
pino: 9.5.0
|
||||
|
||||
hono-rate-limiter@0.4.0(hono@4.6.10):
|
||||
hono-rate-limiter@0.4.0(hono@4.6.11):
|
||||
dependencies:
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
|
||||
hono-zod-openapi@0.4.2(hono@4.6.10)(zod@3.23.8):
|
||||
hono-zod-openapi@0.4.2(hono@4.6.11)(zod@3.23.8):
|
||||
dependencies:
|
||||
'@hono/zod-validator': 0.4.1(hono@4.6.10)(zod@3.23.8)
|
||||
hono: 4.6.10
|
||||
'@hono/zod-validator': 0.4.1(hono@4.6.11)(zod@3.23.8)
|
||||
hono: 4.6.11
|
||||
zod: 3.23.8
|
||||
zod-openapi: 3.3.0(zod@3.23.8)
|
||||
|
||||
hono@4.6.10: {}
|
||||
hono@4.6.11: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
|
|
@ -9634,7 +9634,7 @@ snapshots:
|
|||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-plugin-svelte@3.2.8(prettier@3.3.3)(svelte@5.0.0-next.175):
|
||||
prettier-plugin-svelte@3.3.0(prettier@3.3.3)(svelte@5.0.0-next.175):
|
||||
dependencies:
|
||||
prettier: 3.3.3
|
||||
svelte: 5.0.0-next.175
|
||||
|
|
@ -9962,13 +9962,13 @@ snapshots:
|
|||
|
||||
std-env@3.7.0: {}
|
||||
|
||||
stoker@1.3.0(@asteasolutions/zod-to-openapi@7.1.2(zod@3.23.8))(@hono/zod-openapi@0.15.3(hono@4.6.10)(zod@3.23.8))(hono@4.6.10)(openapi3-ts@4.4.0):
|
||||
stoker@1.3.0(@asteasolutions/zod-to-openapi@7.1.2(zod@3.23.8))(@hono/zod-openapi@0.15.3(hono@4.6.11)(zod@3.23.8))(hono@4.6.11)(openapi3-ts@4.4.0):
|
||||
dependencies:
|
||||
'@asteasolutions/zod-to-openapi': 7.1.2(zod@3.23.8)
|
||||
hono: 4.6.10
|
||||
hono: 4.6.11
|
||||
openapi3-ts: 4.4.0
|
||||
optionalDependencies:
|
||||
'@hono/zod-openapi': 0.15.3(hono@4.6.10)(zod@3.23.8)
|
||||
'@hono/zod-openapi': 0.15.3(hono@4.6.11)(zod@3.23.8)
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
|
|
|
|||
5
src/app.d.ts
vendored
5
src/app.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
import type { ApiClient } from '$lib/server/api';
|
||||
import type { Users } from '$lib/server/api/databases/postgres/tables';
|
||||
import type { Session } from '$lib/server/api/services/sessions.service';
|
||||
import type { parseApiResponse } from '$lib/utils/api';
|
||||
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
|
|
@ -16,8 +17,8 @@ declare global {
|
|||
interface Locals {
|
||||
api: ApiClient['api'];
|
||||
parseApiResponse: typeof parseApiResponse;
|
||||
getAuthedUser: () => Promise<Returned<Users> | null>;
|
||||
getAuthedUserOrThrow: () => Promise<Returned<User>>;
|
||||
getAuthedUser: () => Promise<Returned<Record<Users, Session>> | null>;
|
||||
getAuthedUserOrThrow: () => Promise<Returned<Record<Users, Session>>>;
|
||||
}
|
||||
namespace Superforms {
|
||||
type Message = {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ const apiClient: Handle = async ({ event, resolve }) => {
|
|||
|
||||
/* ----------------------------- Auth functions ----------------------------- */
|
||||
async function getAuthedUser() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse);
|
||||
return data?.user;
|
||||
const { data } = await api.me.$get().then(parseApiResponse);
|
||||
return { user: data?.user, session: data?.session };
|
||||
}
|
||||
|
||||
async function getAuthedUserOrThrow() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse);
|
||||
const { data } = await api.me.$get().then(parseApiResponse);
|
||||
if (!data || !data.user) {
|
||||
throw redirect(StatusCodes.TEMPORARY_REDIRECT, "/");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
|
||||
import type { Session } from '$lib/server/api/services/sessions.service';
|
||||
import type { Hono } from 'hono';
|
||||
import type { PinoLogger } from 'hono-pino';
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
import {StatusCodes} from '$lib/constants/status-codes';
|
||||
import {Controller} from '$lib/server/api/common/types/controller';
|
||||
import {allCollections, getCollectionByCUID, numberOfCollections} from '$lib/server/api/controllers/collection.routes';
|
||||
import {CollectionsService} from '$lib/server/api/services/collections.service';
|
||||
import {openApi} from 'hono-zod-openapi';
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { allCollections, getCollectionByCUID, numberOfCollections } from '$lib/server/api/controllers/collection.routes';
|
||||
import { CollectionsService } from '$lib/server/api/services/collections.service';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { injectable, inject } from '@needle-di/core';
|
||||
import {requireAuth} from '../middleware/require-auth.middleware';
|
||||
import { requireFullAuth } from '../middleware/require-auth.middleware';
|
||||
|
||||
@injectable()
|
||||
export class CollectionController extends Controller {
|
||||
constructor(private collectionsService = inject(CollectionsService)) {
|
||||
super();
|
||||
}
|
||||
constructor(private collectionsService = inject(CollectionsService)) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, openApi(allCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id);
|
||||
console.log('collections service', collections);
|
||||
return c.json({ collections }, StatusCodes.OK);
|
||||
})
|
||||
.get('/count', requireAuth, openApi(numberOfCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id);
|
||||
return c.json({ count: collections?.length || 0 }, StatusCodes.OK);
|
||||
})
|
||||
.get('/:cuid', requireAuth, openApi(getCollectionByCUID), async (c) => {
|
||||
const cuid = c.req.param('cuid');
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid);
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireFullAuth, openApi(allCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id);
|
||||
console.log('collections service', collections);
|
||||
return c.json({ collections }, StatusCodes.OK);
|
||||
})
|
||||
.get('/count', requireFullAuth, openApi(numberOfCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id);
|
||||
return c.json({ count: collections?.length || 0 }, StatusCodes.OK);
|
||||
})
|
||||
.get('/:cuid', requireFullAuth, openApi(getCollectionByCUID), async (c) => {
|
||||
const cuid = c.req.param('cuid');
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid);
|
||||
|
||||
if (!collection) {
|
||||
return c.json('Collection not found', StatusCodes.NOT_FOUND);
|
||||
}
|
||||
if (!collection) {
|
||||
return c.json('Collection not found', StatusCodes.NOT_FOUND);
|
||||
}
|
||||
|
||||
return c.json({ collection }, StatusCodes.OK);
|
||||
});
|
||||
}
|
||||
return c.json({ collection }, StatusCodes.OK);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,116 +1,119 @@
|
|||
import {StatusCodes} from '$lib/constants/status-codes';
|
||||
import {Controller} from '$lib/server/api/common/types/controller';
|
||||
import {createBlankSessionTokenCookie, setSessionCookie} from '$lib/server/api/common/utils/cookies';
|
||||
import {changePasswordDto} from '$lib/server/api/dtos/change-password.dto';
|
||||
import {updateEmailDto} from '$lib/server/api/dtos/update-email.dto';
|
||||
import {updateProfileDto} from '$lib/server/api/dtos/update-profile.dto';
|
||||
import {verifyPasswordDto} from '$lib/server/api/dtos/verify-password.dto';
|
||||
import {limiter} from '$lib/server/api/middleware/rate-limiter.middleware';
|
||||
import {IamService} from '$lib/server/api/services/iam.service';
|
||||
import {LoginRequestsService} from '$lib/server/api/services/loginrequest.service';
|
||||
import {SessionsService} from '$lib/server/api/services/sessions.service';
|
||||
import {zValidator} from '@hono/zod-validator';
|
||||
import {openApi} from 'hono-zod-openapi';
|
||||
import { injectable, inject } from "@needle-di/core";
|
||||
import {requireAuth} from '../middleware/require-auth.middleware';
|
||||
import {iam, logout, updateEmail, updatePassword, updateProfile, verifyPassword} from './iam.routes';
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { createBlankSessionTokenCookie, setSessionCookie } from '$lib/server/api/common/utils/cookies';
|
||||
import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto';
|
||||
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto';
|
||||
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
||||
import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto';
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
|
||||
import { IamService } from '$lib/server/api/services/iam.service';
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { injectable, inject } from '@needle-di/core';
|
||||
import { requireFullAuth, requireTempAuth } from '../middleware/require-auth.middleware';
|
||||
import { iam, logout, updateEmail, updatePassword, updateProfile, verifyPassword } from './iam.routes';
|
||||
import { UsersRepository } from '../repositories/users.repository';
|
||||
|
||||
@injectable()
|
||||
export class IamController extends Controller {
|
||||
constructor(
|
||||
private iamService = inject(IamService),
|
||||
private loginRequestService = inject(LoginRequestsService),
|
||||
private sessionsService = inject(SessionsService),
|
||||
) {
|
||||
super();
|
||||
}
|
||||
constructor(
|
||||
private iamService = inject(IamService),
|
||||
private loginRequestService = inject(LoginRequestsService),
|
||||
private sessionsService = inject(SessionsService),
|
||||
private usersRepository = inject(UsersRepository),
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, openApi(iam), async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
})
|
||||
.put(
|
||||
'/update/profile',
|
||||
requireAuth,
|
||||
openApi(updateProfile),
|
||||
zValidator('json', updateProfileDto),
|
||||
limiter({ limit: 30, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { firstName, lastName, username } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
|
||||
if (!updatedUser) {
|
||||
return c.json('Username already in use', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'/verify/password',
|
||||
requireAuth,
|
||||
zValidator('json', verifyPasswordDto),
|
||||
openApi(verifyPassword),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password } = c.req.valid('json');
|
||||
const passwordVerified = await this.iamService.verifyPassword(user.id, { password });
|
||||
if (!passwordVerified) {
|
||||
console.log('Incorrect password');
|
||||
return c.json('Incorrect password', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({}, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
'/update/password',
|
||||
requireAuth,
|
||||
openApi(updatePassword),
|
||||
zValidator('json', changePasswordDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password, confirm_password } = c.req.valid('json');
|
||||
if (password !== confirm_password) {
|
||||
return c.json('Passwords do not match', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
try {
|
||||
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
||||
await this.sessionsService.invalidateSession(user.id);
|
||||
await this.loginRequestService.createUserSession(user.id, c.req, false);
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error);
|
||||
return c.json('Error updating password', StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'/update/email',
|
||||
requireAuth,
|
||||
openApi(updateEmail),
|
||||
zValidator('json', updateEmailDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { email } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateEmail(user.id, { email });
|
||||
if (!updatedUser) {
|
||||
return c.json('Cannot change email address', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.post('/logout', requireAuth, openApi(logout), async (c) => {
|
||||
const sessionId = c.var.session.id;
|
||||
await this.iamService.logout(sessionId);
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
});
|
||||
}
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', openApi(iam), async (c) => {
|
||||
const session = c.var.session;
|
||||
const user = session ? await this.usersRepository.findOneByIdOrThrow(session.userId) : null;
|
||||
return c.json({ user, session });
|
||||
})
|
||||
.put(
|
||||
'/update/profile',
|
||||
requireFullAuth,
|
||||
openApi(updateProfile),
|
||||
zValidator('json', updateProfileDto),
|
||||
limiter({ limit: 30, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { firstName, lastName, username } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
|
||||
if (!updatedUser) {
|
||||
return c.json('Username already in use', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'/verify/password',
|
||||
requireFullAuth,
|
||||
zValidator('json', verifyPasswordDto),
|
||||
openApi(verifyPassword),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password } = c.req.valid('json');
|
||||
const passwordVerified = await this.iamService.verifyPassword(user.id, { password });
|
||||
if (!passwordVerified) {
|
||||
console.log('Incorrect password');
|
||||
return c.json('Incorrect password', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({}, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
'/update/password',
|
||||
requireFullAuth,
|
||||
openApi(updatePassword),
|
||||
zValidator('json', changePasswordDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password, confirm_password } = c.req.valid('json');
|
||||
if (password !== confirm_password) {
|
||||
return c.json('Passwords do not match', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
try {
|
||||
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
||||
await this.sessionsService.invalidateSession(user.id);
|
||||
await this.loginRequestService.createUserSession(user.id, c.req, false, false);
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error);
|
||||
return c.json('Error updating password', StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'/update/email',
|
||||
requireFullAuth,
|
||||
openApi(updateEmail),
|
||||
zValidator('json', updateEmailDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { email } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateEmail(user.id, { email });
|
||||
if (!updatedUser) {
|
||||
return c.json('Cannot change email address', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.post('/logout', requireFullAuth, openApi(logout), async (c) => {
|
||||
const sessionId = c.var.session.id;
|
||||
await this.iamService.logout(sessionId);
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
import {Controller} from '$lib/server/api/common/types/controller';
|
||||
import {cookieExpiresAt, createSessionTokenCookie, setSessionCookie} from '$lib/server/api/common/utils/cookies';
|
||||
import {signinUsernameDto} from '$lib/server/api/dtos/signin-username.dto';
|
||||
import {SessionsService} from '$lib/server/api/services/sessions.service';
|
||||
import {zValidator} from '@hono/zod-validator';
|
||||
import {openApi} from 'hono-zod-openapi';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { cookieExpiresAt, createSessionTokenCookie, setSessionCookie } from '$lib/server/api/common/utils/cookies';
|
||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import {limiter} from '../middleware/rate-limiter.middleware';
|
||||
import {LoginRequestsService} from '../services/loginrequest.service';
|
||||
import {signinUsername} from './login.routes';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||
import { signinUsername } from './login.routes';
|
||||
|
||||
@injectable()
|
||||
export class LoginController extends Controller {
|
||||
constructor(
|
||||
private loginRequestsService = inject(LoginRequestsService),
|
||||
private sessionsService = inject(SessionsService),
|
||||
) {
|
||||
super();
|
||||
}
|
||||
constructor(
|
||||
private loginRequestsService = inject(LoginRequestsService),
|
||||
private sessionsService = inject(SessionsService),
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller.post(
|
||||
'/',
|
||||
openApi(signinUsername),
|
||||
zValidator('json', signinUsernameDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const { username, password } = c.req.valid('json');
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ message: 'ok' });
|
||||
},
|
||||
);
|
||||
}
|
||||
routes() {
|
||||
return this.controller.post(
|
||||
'/',
|
||||
openApi(signinUsername),
|
||||
zValidator('json', signinUsernameDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const { username, password } = c.req.valid('json');
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ message: 'ok' });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { UsersService } from '$lib/server/api/services/users.service';
|
|||
import { zValidator } from '@hono/zod-validator';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import { CredentialsType } from '../databases/postgres/tables';
|
||||
import { requireAuth } from '../middleware/require-auth.middleware';
|
||||
import { requireFullAuth, requireTempAuth } from '../middleware/require-auth.middleware';
|
||||
import { createTwoFactorSchema } from '../dtos/create-totp.dto';
|
||||
import { decodeBase64 } from '@oslojs/encoding';
|
||||
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||
|
|
@ -26,12 +26,13 @@ export class MfaController extends Controller {
|
|||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/totp', requireAuth, async (c) => {
|
||||
.get('/totp', requireTempAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
c.var.logger.info(`The user ${user.id} is requesting TOTP credentials`);
|
||||
const totpCredential = await this.totpService.findOneByUserId(user.id);
|
||||
return c.json({ totpCredential });
|
||||
})
|
||||
.post('/totp', requireAuth, zValidator('json', createTwoFactorSchema), async (c) => {
|
||||
.post('/totp', requireTempAuth, zValidator('json', createTwoFactorSchema), async (c) => {
|
||||
const user = c.var.user;
|
||||
const { key } = c.req.valid('json');
|
||||
const totpCredential = await this.totpService.create(user.id, decodeBase64(key));
|
||||
|
|
@ -41,7 +42,7 @@ export class MfaController extends Controller {
|
|||
}
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
.delete('/totp', requireAuth, async (c) => {
|
||||
.delete('/totp', requireFullAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
try {
|
||||
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP);
|
||||
|
|
@ -54,20 +55,20 @@ export class MfaController extends Controller {
|
|||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.get('/totp/recoveryCodes', requireAuth, async (c) => {
|
||||
.get('/totp/recoveryCodes', requireFullAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
// You can only view recovery codes once and that is on creation
|
||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id);
|
||||
if (existingCodes && existingCodes.length > 0) {
|
||||
console.log('Recovery Codes found', existingCodes);
|
||||
// Filter out codes that are not used and only return the code
|
||||
const codes = existingCodes.filter(code => !code.used).map(code => code.code);
|
||||
const codes = existingCodes.filter((code) => !code.used).map((code) => code.code);
|
||||
return c.json({ recoveryCodes: codes });
|
||||
}
|
||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id);
|
||||
return c.json({ recoveryCodes });
|
||||
})
|
||||
.post('/totp/recoveryCodes', requireAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
.post('/totp/recoveryCodes', requireFullAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
try {
|
||||
const user = c.var.user;
|
||||
const { code } = c.req.valid('json');
|
||||
|
|
@ -82,7 +83,7 @@ export class MfaController extends Controller {
|
|||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
.post('/totp/verify', requireTempAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
try {
|
||||
const user = c.var.user;
|
||||
const { code } = c.req.valid('json');
|
||||
|
|
@ -90,7 +91,7 @@ export class MfaController extends Controller {
|
|||
const verified = await this.totpService.verify(user.id, code);
|
||||
if (verified) {
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: true });
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, true);
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, true, true);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export class SignupController extends Controller {
|
|||
return c.body('Failed to create user', 500);
|
||||
}
|
||||
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, false);
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, false, false);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
import {Controller} from '$lib/server/api/common/types/controller';
|
||||
import {UsersService} from '$lib/server/api/services/users.service';
|
||||
import {inject, injectable} from '@needle-di/core';
|
||||
import {requireAuth} from '../middleware/require-auth.middleware';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { UsersService } from '$lib/server/api/services/users.service';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import { requireFullAuth, requireTempAuth } from '../middleware/require-auth.middleware';
|
||||
|
||||
@injectable()
|
||||
export class UserController extends Controller {
|
||||
constructor(private usersService = inject(UsersService)) {
|
||||
super();
|
||||
}
|
||||
constructor(private usersService = inject(UsersService)) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
})
|
||||
.get('/:id', requireAuth, async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await this.usersService.findOneById(id);
|
||||
return c.json({ user });
|
||||
})
|
||||
.get('/username/:userName', requireAuth, async (c) => {
|
||||
const userName = c.req.param('userName');
|
||||
const user = await this.usersService.findOneByUsername(userName);
|
||||
return c.json({ user });
|
||||
});
|
||||
}
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireTempAuth, async (c) => {
|
||||
const session = c.var.session;
|
||||
const user = session ? await this.usersService.findOneById(session.userId) : null;
|
||||
return c.json({ user, session });
|
||||
})
|
||||
.get('/:id', requireFullAuth, async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await this.usersService.findOneById(id);
|
||||
return c.json({ user });
|
||||
})
|
||||
.get('/username/:userName', requireFullAuth, async (c) => {
|
||||
const userName = c.req.param('userName');
|
||||
const user = await this.usersService.findOneByUsername(userName);
|
||||
return c.json({ user });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import {Controller} from '$lib/server/api/common/types/controller'
|
||||
import {WishlistsService} from '$lib/server/api/services/wishlists.service'
|
||||
import {inject, injectable} from '@needle-di/core'
|
||||
import {requireAuth} from '../middleware/require-auth.middleware'
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { WishlistsService } from '$lib/server/api/services/wishlists.service';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import { requireFullAuth } from '../middleware/require-auth.middleware';
|
||||
|
||||
@injectable()
|
||||
export class WishlistController extends Controller {
|
||||
constructor(private wishlistsService = inject(WishlistsService)) {
|
||||
super()
|
||||
}
|
||||
constructor(private wishlistsService = inject(WishlistsService)) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const wishlists = await this.wishlistsService.findAllByUserId(user.id)
|
||||
return c.json({ wishlists })
|
||||
})
|
||||
.get('/:cuid', requireAuth, async (c) => {
|
||||
const cuid = c.req.param('cuid')
|
||||
const wishlist = await this.wishlistsService.findOneByCuid(cuid)
|
||||
return c.json({ wishlist })
|
||||
})
|
||||
}
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireFullAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
const wishlists = await this.wishlistsService.findAllByUserId(user.id);
|
||||
return c.json({ wishlists });
|
||||
})
|
||||
.get('/:cuid', requireFullAuth, async (c) => {
|
||||
const cuid = c.req.param('cuid');
|
||||
const wishlist = await this.wishlistsService.findOneByCuid(cuid);
|
||||
return c.json({ wishlist });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ for (const table of [
|
|||
schema.publishers_to_games,
|
||||
schema.recoveryCodesTable,
|
||||
schema.rolesTable,
|
||||
schema.sessionsTable,
|
||||
schema.twoFactorTable,
|
||||
schema.user_roles,
|
||||
schema.usersTable,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export * from './publishersToExternalIds.table'
|
|||
export * from './publishersToGames.table'
|
||||
export * from './recovery-codes.table'
|
||||
export * from './roles.table'
|
||||
export * from './sessions.table'
|
||||
export * from './two-factor.table'
|
||||
export * from './userRoles.table'
|
||||
export * from './users.table'
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import {type InferSelectModel, relations} from 'drizzle-orm';
|
||||
import {boolean, pgTable, text, timestamp, uuid} from 'drizzle-orm/pg-core';
|
||||
import {cuid2} from '../../../common/utils/table';
|
||||
import {usersTable} from './users.table';
|
||||
|
||||
export const sessionsTable = pgTable('sessions', {
|
||||
id: cuid2().primaryKey(),
|
||||
userId: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
expiresAt: timestamp({
|
||||
withTimezone: true,
|
||||
mode: 'date',
|
||||
}).notNull(),
|
||||
ipCountry: text(),
|
||||
ipAddress: text(),
|
||||
twoFactorAuthEnabled: boolean().default(false),
|
||||
isTwoFactorAuthenticated: boolean().default(false),
|
||||
});
|
||||
|
||||
export const sessionsRelations = relations(sessionsTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [sessionsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export type Sessions = InferSelectModel<typeof sessionsTable>;
|
||||
|
|
@ -1,16 +1,30 @@
|
|||
import {Unauthorized} from '$lib/server/api/common/exceptions';
|
||||
import type {Sessions} from '$lib/server/api/databases/postgres/tables';
|
||||
import type {MiddlewareHandler} from 'hono';
|
||||
import {createMiddleware} from 'hono/factory';
|
||||
import type {User} from 'lucia';
|
||||
import { Unauthorized } from '$lib/server/api/common/exceptions';
|
||||
import type { Sessions, Users } from '$lib/server/api/databases/postgres/tables';
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { createMiddleware } from 'hono/factory';
|
||||
|
||||
export const requireAuth: MiddlewareHandler<{
|
||||
export const requireFullAuth: MiddlewareHandler<{
|
||||
Variables: {
|
||||
session: Sessions;
|
||||
user: Users;
|
||||
};
|
||||
}> = createMiddleware(async (c, next) => {
|
||||
const session = c.var.session;
|
||||
if (!session || (session?.twoFactorAuthEnabled && !session?.twoFactorVerified)) {
|
||||
throw Unauthorized('You must be logged in to access this resource');
|
||||
}
|
||||
return next();
|
||||
});
|
||||
|
||||
export const requireTempAuth: MiddlewareHandler<{
|
||||
Variables: {
|
||||
session: Sessions;
|
||||
user: User;
|
||||
user: Users;
|
||||
};
|
||||
}> = createMiddleware(async (c, next) => {
|
||||
const user = c.var.user;
|
||||
if (!user) throw Unauthorized('You must be logged in to access this resource');
|
||||
const session = c.var.session;
|
||||
if (!session) {
|
||||
throw Unauthorized('You must be logged in to access this resource');
|
||||
}
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@ import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto
|
|||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import type { HonoRequest } from 'hono';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import { BadRequest } from '../common/exceptions';
|
||||
import { BadRequest, NotFound } from '../common/exceptions';
|
||||
import type { Credentials } from '../databases/postgres/tables';
|
||||
import { CredentialsRepository } from '../repositories/credentials.repository';
|
||||
import { UsersRepository } from '../repositories/users.repository';
|
||||
|
|
@ -39,7 +39,7 @@ export class LoginRequestsService {
|
|||
const existingUser = await this.usersRepository.findOneByUsername(data.username);
|
||||
|
||||
if (!existingUser) {
|
||||
throw BadRequest('User not found');
|
||||
throw NotFound('User not found');
|
||||
}
|
||||
|
||||
const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id);
|
||||
|
|
@ -54,10 +54,15 @@ export class LoginRequestsService {
|
|||
|
||||
const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id);
|
||||
|
||||
return await this.createUserSession(existingUser.id, req, !!totpCredentials && totpCredentials.secret_data !== null && totpCredentials.secret_data !== '');
|
||||
return await this.createUserSession(
|
||||
existingUser.id,
|
||||
req,
|
||||
!!totpCredentials && totpCredentials.secret_data !== null && totpCredentials.secret_data !== '',
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async createUserSession(existingUserId: string, req: HonoRequest, twoFactorAuthEnabled: boolean) {
|
||||
async createUserSession(existingUserId: string, req: HonoRequest, twoFactorAuthEnabled: boolean, twoFactorVerified = false) {
|
||||
const requestIpAddress = req.header('X-Forwarded-For');
|
||||
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||
return this.sessionsService.createSession(
|
||||
|
|
@ -66,7 +71,7 @@ export class LoginRequestsService {
|
|||
requestIpCountry || 'unknown',
|
||||
requestIpAddress || 'unknown',
|
||||
twoFactorAuthEnabled,
|
||||
false,
|
||||
twoFactorVerified,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,15 +5,36 @@ import type {Disposable} from 'tsyringe';
|
|||
|
||||
@injectable()
|
||||
export class RedisService implements Disposable {
|
||||
readonly client: Redis
|
||||
readonly client: Redis;
|
||||
|
||||
constructor() {
|
||||
this.client = new Redis(config.redis.url, {
|
||||
maxRetriesPerRequest: null,
|
||||
})
|
||||
}
|
||||
constructor() {
|
||||
this.client = new Redis(config.redis.url, {
|
||||
maxRetriesPerRequest: null,
|
||||
});
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.client.disconnect()
|
||||
}
|
||||
async get(data: { prefix: string; key: string }): Promise<string | null> {
|
||||
return this.client.get(`${data.prefix}:${data.key}`);
|
||||
}
|
||||
|
||||
async set(data: { prefix: string; key: string; value: string }): Promise<void> {
|
||||
await this.client.set(`${data.prefix}:${data.key}`, data.value);
|
||||
}
|
||||
|
||||
async delete(data: { prefix: string; key: string }): Promise<void> {
|
||||
await this.client.del(`${data.prefix}:${data.key}`);
|
||||
}
|
||||
|
||||
async setWithExpiry(data: {
|
||||
prefix: string;
|
||||
key: string;
|
||||
value: string;
|
||||
expiry: number;
|
||||
}): Promise<void> {
|
||||
await this.client.set(`${data.prefix}:${data.key}`, data.value, 'EXAT', Math.floor(data.expiry));
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.client.disconnect();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,119 +17,119 @@ export type RedisSession = {
|
|||
};
|
||||
|
||||
export type Session = {
|
||||
id: string;
|
||||
userId: string;
|
||||
expiresAt: Date;
|
||||
ipCountry: string;
|
||||
ipAddress: string;
|
||||
twoFactorAuthEnabled: boolean;
|
||||
isTwoFactorAuthenticated: boolean;
|
||||
id: string;
|
||||
userId: string;
|
||||
expiresAt: Date;
|
||||
ipCountry: string;
|
||||
ipAddress: string;
|
||||
twoFactorEnabled: boolean;
|
||||
twoFactorVerified: boolean;
|
||||
};
|
||||
|
||||
export type SessionValidationResult = { session: Session; user: Users } | { session: null; user: null } | { session: Session; user: undefined };
|
||||
|
||||
@injectable()
|
||||
export class SessionsService {
|
||||
constructor(
|
||||
private redisService = inject(RedisService),
|
||||
private usersRepository = inject(UsersRepository),
|
||||
) {}
|
||||
constructor(
|
||||
private redisService = inject(RedisService),
|
||||
private usersRepository = inject(UsersRepository),
|
||||
) {}
|
||||
|
||||
generateSessionToken() {
|
||||
const bytes = new Uint8Array(20);
|
||||
crypto.getRandomValues(bytes);
|
||||
return encodeBase32LowerCaseNoPadding(bytes);
|
||||
}
|
||||
generateSessionToken() {
|
||||
const bytes = new Uint8Array(20);
|
||||
crypto.getRandomValues(bytes);
|
||||
return encodeBase32LowerCaseNoPadding(bytes);
|
||||
}
|
||||
|
||||
async createSession(
|
||||
token: string,
|
||||
userId: string,
|
||||
ipCountry: string,
|
||||
ipAddress: string,
|
||||
twoFactorAuthEnabled: boolean,
|
||||
isTwoFactorAuthenticated: boolean,
|
||||
) {
|
||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const session = {
|
||||
id: sessionId,
|
||||
userId,
|
||||
expiresAt: cookieExpiresAt,
|
||||
ipCountry,
|
||||
ipAddress,
|
||||
twoFactorAuthEnabled,
|
||||
isTwoFactorAuthenticated,
|
||||
};
|
||||
await this.redisService.client.set(
|
||||
`session:${sessionId}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: session.expiresAt,
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_auth_enabled: session.twoFactorAuthEnabled,
|
||||
is_two_factor_authenticated: session.isTwoFactorAuthenticated,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
return session;
|
||||
}
|
||||
async createSession(
|
||||
token: string,
|
||||
userId: string,
|
||||
ipCountry: string,
|
||||
ipAddress: string,
|
||||
twoFactorAuthEnabled: boolean,
|
||||
twoFactorVerified: boolean,
|
||||
) {
|
||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const session = {
|
||||
id: sessionId,
|
||||
userId,
|
||||
expiresAt: cookieExpiresAt,
|
||||
ipCountry,
|
||||
ipAddress,
|
||||
twoFactorAuthEnabled,
|
||||
twoFactorVerified,
|
||||
};
|
||||
await this.redisService.client.set(
|
||||
`session:${sessionId}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: session.expiresAt,
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_auth_enabled: session.twoFactorAuthEnabled,
|
||||
is_two_factor_authenticated: session.twoFactorVerified,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
return session;
|
||||
}
|
||||
|
||||
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
||||
// TODO: Why was this needed in the docs? https://lucia-next.pages.dev/sessions/basic-api/drizzle-orm
|
||||
// const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const item = await this.redisService.client.get(`session:${token}`);
|
||||
if (item === null) {
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
const result: RedisSession = JSON.parse(item);
|
||||
const session: Session = {
|
||||
id: result.id,
|
||||
userId: result.user_id,
|
||||
expiresAt: new Date(result.expires_at * 1000),
|
||||
ipCountry: result.ip_country,
|
||||
ipAddress: result.ip_address,
|
||||
twoFactorAuthEnabled: result.two_factor_auth_enabled,
|
||||
isTwoFactorAuthenticated: result.is_two_factor_authenticated,
|
||||
};
|
||||
let user: Users | undefined = undefined;
|
||||
if (session.userId) {
|
||||
user = await this.usersRepository.findOneById(session.userId);
|
||||
}
|
||||
if (Date.now() >= session.expiresAt.getTime()) {
|
||||
await this.redisService.client.del(`session:${token}`);
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
||||
// TODO: Why was this needed in the docs? https://lucia-next.pages.dev/sessions/basic-api/drizzle-orm
|
||||
// const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const item = await this.redisService.client.get(`session:${token}`);
|
||||
if (item === null) {
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
const result: RedisSession = JSON.parse(item);
|
||||
const session: Session = {
|
||||
id: result.id,
|
||||
userId: result.user_id,
|
||||
expiresAt: new Date(result.expires_at * 1000),
|
||||
ipCountry: result.ip_country,
|
||||
ipAddress: result.ip_address,
|
||||
twoFactorEnabled: result.two_factor_auth_enabled,
|
||||
twoFactorVerified: result.is_two_factor_authenticated,
|
||||
};
|
||||
let user: Users | undefined = undefined;
|
||||
if (session.userId) {
|
||||
user = await this.usersRepository.findOneById(session.userId);
|
||||
}
|
||||
if (Date.now() >= session.expiresAt.getTime()) {
|
||||
await this.redisService.client.del(`session:${token}`);
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (Date.now() >= session.expiresAt.getTime() - cookieExpiresMilliseconds) {
|
||||
session.expiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
|
||||
await this.redisService.client.set(
|
||||
`session:${token}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: Math.floor(session.expiresAt.getTime() / 1000),
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_auth_enabled: session.twoFactorAuthEnabled,
|
||||
is_two_factor_authenticated: session.isTwoFactorAuthenticated,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
}
|
||||
if (Date.now() >= session.expiresAt.getTime() - cookieExpiresMilliseconds) {
|
||||
session.expiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
|
||||
await this.redisService.client.set(
|
||||
`session:${token}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: Math.floor(session.expiresAt.getTime() / 1000),
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_enabled: session.twoFactorEnabled,
|
||||
two_factor_verified: session.twoFactorVerified,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
}
|
||||
|
||||
return { session, user };
|
||||
}
|
||||
return { session, user };
|
||||
}
|
||||
|
||||
async invalidateSession(sessionId: string) {
|
||||
await this.redisService.client.del(`session:${sessionId}`);
|
||||
}
|
||||
async invalidateSession(sessionId: string) {
|
||||
await this.redisService.client.del(`session:${sessionId}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import {eq} from 'drizzle-orm';
|
||||
import {generateIdFromEntropySize, type Session, type User} from 'lucia';
|
||||
import {createDate, TimeSpan} from 'oslo';
|
||||
import {password_reset_tokens} from './api/databases/postgres/tables';
|
||||
import {db} from './api/packages/drizzle';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { generateIdFromEntropySize } from 'lucia';
|
||||
import { createDate, TimeSpan } from 'oslo';
|
||||
import { password_reset_tokens, type Users } from './api/databases/postgres/tables';
|
||||
import { db } from './api/packages/drizzle';
|
||||
import type { Session } from './api/services/sessions.service';
|
||||
|
||||
export async function createPasswordResetToken(userId: string): Promise<string> {
|
||||
// optionally invalidate all existing tokens
|
||||
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.user_id, userId));
|
||||
const tokenId = generateIdFromEntropySize(40);
|
||||
await db.insert(password_reset_tokens).values({
|
||||
id: tokenId,
|
||||
user_id: userId,
|
||||
expires_at: createDate(new TimeSpan(2, 'h')),
|
||||
});
|
||||
return tokenId;
|
||||
// optionally invalidate all existing tokens
|
||||
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.user_id, userId));
|
||||
const tokenId = generateIdFromEntropySize(40);
|
||||
await db.insert(password_reset_tokens).values({
|
||||
id: tokenId,
|
||||
user_id: userId,
|
||||
expires_at: createDate(new TimeSpan(2, 'h')),
|
||||
});
|
||||
return tokenId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -23,8 +24,8 @@ export async function createPasswordResetToken(userId: string): Promise<string>
|
|||
* @param session - The session object.
|
||||
* @returns True if the user is not fully authenticated, otherwise false.
|
||||
*/
|
||||
export function userNotFullyAuthenticated(user: User | null, session: Session | null) {
|
||||
return user && session && session.isTwoFactorAuthEnabled && !session.isTwoFactorAuthenticated;
|
||||
export function userNotFullyAuthenticated(user: Users | null, session: Session | null) {
|
||||
return user && session && session.twoFactorEnabled && !session.twoFactorVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,7 +36,7 @@ export function userNotFullyAuthenticated(user: User | null, session: Session |
|
|||
* @returns {boolean} True if the user is not fully authenticated, otherwise false.
|
||||
*/
|
||||
export function userNotAuthenticated(user: User | null, session: Session | null) {
|
||||
return !user || !session || userNotFullyAuthenticated(user, session);
|
||||
return !user || !session || userNotFullyAuthenticated(user, session);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,5 +47,5 @@ export function userNotAuthenticated(user: User | null, session: Session | null)
|
|||
* @returns {boolean} True if the user is fully authenticated, otherwise false.
|
||||
*/
|
||||
export function userFullyAuthenticated(user: User | null, session: Session | null) {
|
||||
return !userNotAuthenticated(user, session);
|
||||
return !userNotAuthenticated(user, session);
|
||||
}
|
||||
|
|
|
|||
28
src/routes/(app)/(protected)/+layout.server.ts
Normal file
28
src/routes/(app)/(protected)/+layout.server.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { loadFlash } from 'sveltekit-flash-message/server';
|
||||
import type { LayoutServerLoad } from '../$types';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
|
||||
export const load: LayoutServerLoad = loadFlash(async (event) => {
|
||||
const { locals, url } = event;
|
||||
|
||||
const { user, session } = await locals.getAuthedUser();
|
||||
console.log('User from protected route', user);
|
||||
console.log('Session from protected route', session);
|
||||
if (session === null) {
|
||||
throw redirect(302, '/landing', notSignedInMessage, event);
|
||||
}
|
||||
if (session?.twoFactorEnabled && !session?.twoFactorVerified) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
return {
|
||||
url: url.pathname,
|
||||
user: {
|
||||
cuid: user?.cuid,
|
||||
firstName: user?.firstName,
|
||||
lastName: user?.lastName,
|
||||
username: user?.username,
|
||||
},
|
||||
};
|
||||
});
|
||||
52
src/routes/(app)/(protected)/+page.server.ts
Normal file
52
src/routes/(app)/(protected)/+page.server.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { fail } from '@sveltejs/kit';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from '../$types';
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals, url } = event;
|
||||
|
||||
const image = {
|
||||
url: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
locale: 'en_US',
|
||||
title: 'Home',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
images: [image],
|
||||
siteName: 'Bored Game',
|
||||
},
|
||||
twitter: {
|
||||
handle: '@boredgame',
|
||||
site: '@boredgame',
|
||||
cardType: 'summary_large_image',
|
||||
title: 'Home | Bored Game',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
image: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
imageAlt: 'Home | Bored Game',
|
||||
},
|
||||
});
|
||||
|
||||
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse);
|
||||
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse);
|
||||
|
||||
if (wishlistsError || collectionsError) {
|
||||
return fail(500);
|
||||
}
|
||||
|
||||
console.log('Wishlists', wishlistsData.wishlists);
|
||||
console.log('Collections', collectionsData.collections);
|
||||
return {
|
||||
metaTagsChild: metaTags,
|
||||
wishlists: wishlistsData.wishlists,
|
||||
collections: collectionsData.collections,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
<script lang="ts">
|
||||
const { data } = $props()
|
||||
const { user, wishlists = [], collections = [] } = data
|
||||
import { page } from "$app/stores";
|
||||
import type { PageData } from './$types';
|
||||
|
||||
const welcomeName = $derived.by(() => {
|
||||
let welcomeName = ''
|
||||
if (data?.user?.firstName) {
|
||||
welcomeName += data?.user?.firstName
|
||||
}
|
||||
if (data?.user?.lastName) {
|
||||
welcomeName = welcomeName.length === 0 ? data?.user?.lastName : welcomeName
|
||||
}
|
||||
let { data }: { data: PageData } = $props();
|
||||
const { user, wishlists = [], collections = [] } = data;
|
||||
|
||||
if (welcomeName.length === 0) {
|
||||
return user?.username
|
||||
}
|
||||
const welcomeName = $derived.by(() => {
|
||||
let welcomeName = "";
|
||||
if (data?.user?.firstName) {
|
||||
welcomeName += data?.user?.firstName;
|
||||
}
|
||||
if (data?.user?.lastName) {
|
||||
welcomeName = welcomeName.length === 0 ? data?.user?.lastName : welcomeName;
|
||||
}
|
||||
|
||||
return welcomeName
|
||||
})
|
||||
if (welcomeName.length === 0) {
|
||||
return user?.username;
|
||||
}
|
||||
|
||||
return welcomeName;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
|
@ -37,7 +40,10 @@ const welcomeName = $derived.by(() => {
|
|||
{:else}
|
||||
<h1>Welcome to Bored Game!</h1>
|
||||
<h2>Track the board games you own, the ones you want, and whether you play them enough.</h2>
|
||||
<p>Get started by joining the <a href="/waitlist">wait list</a> or <a href="/login">log in</a> if you already have an account.</p>
|
||||
<p>
|
||||
Get started by joining the <a href="/waitlist">wait list</a> or <a href="/login">log in</a> if you already have an
|
||||
account.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
39
src/routes/(app)/(public)/landing/+page.server.ts
Normal file
39
src/routes/(app)/(public)/landing/+page.server.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from '../$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals, url } = event;
|
||||
|
||||
const image = {
|
||||
url: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
locale: 'en_US',
|
||||
title: 'Home',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
images: [image],
|
||||
siteName: 'Bored Game',
|
||||
},
|
||||
twitter: {
|
||||
handle: '@boredgame',
|
||||
site: '@boredgame',
|
||||
cardType: 'summary_large_image',
|
||||
title: 'Home | Bored Game',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
image: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
imageAlt: 'Home | Bored Game',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
metaTagsChild: metaTags,
|
||||
};
|
||||
};
|
||||
26
src/routes/(app)/(public)/landing/+page.svelte
Normal file
26
src/routes/(app)/(public)/landing/+page.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
const { data } = $props();
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h1>Welcome to Bored Game!</h1>
|
||||
<h2>Track the board games you own, the ones you want, and whether you play them enough.</h2>
|
||||
<p>
|
||||
Get started by joining the <a href="/waitlist">wait list</a> or <a href="/login">log in</a> if you already have an account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
max-width: 900px;
|
||||
gap: 0.25rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,10 +3,15 @@ import type { LayoutServerLoad } from '../$types';
|
|||
|
||||
export const load: LayoutServerLoad = loadFlash(async (event) => {
|
||||
const { url, locals } = event;
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
return {
|
||||
url: url.pathname,
|
||||
authedUser,
|
||||
user: user ? {
|
||||
cuid: user?.cuid,
|
||||
firstName: user?.firstName,
|
||||
lastName: user?.lastName,
|
||||
username: user?.username,
|
||||
} : null,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const { data, children } = $props();
|
|||
</script>
|
||||
|
||||
<div class="flex min-h-screen w-full flex-col">
|
||||
<Header user={data.authedUser} />
|
||||
<Header user={data.user} />
|
||||
|
||||
<main
|
||||
class="flex min-h-[calc(100vh-theme(spacing.16))] flex-1 flex-col gap-4 p-4 md:gap-8 md:p-10"
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
import { fail } from '@sveltejs/kit';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals, url } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
|
||||
const image = {
|
||||
url: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
locale: 'en_US',
|
||||
title: 'Home',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
images: [image],
|
||||
siteName: 'Bored Game',
|
||||
},
|
||||
twitter: {
|
||||
handle: '@boredgame',
|
||||
site: '@boredgame',
|
||||
cardType: 'summary_large_image',
|
||||
title: 'Home | Bored Game',
|
||||
description: 'Bored Game, keep track of your games',
|
||||
image: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
imageAlt: 'Home | Bored Game',
|
||||
},
|
||||
});
|
||||
|
||||
if (authedUser) {
|
||||
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse);
|
||||
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse);
|
||||
|
||||
if (wishlistsError || collectionsError) {
|
||||
return fail(500, 'Failed to fetch wishlistsTable or collections');
|
||||
}
|
||||
|
||||
console.log('Wishlists', wishlistsData.wishlists);
|
||||
console.log('Collections', collectionsData.collections);
|
||||
return {
|
||||
metaTagsChild: metaTags,
|
||||
user: {
|
||||
firstName: authedUser?.firstName,
|
||||
lastName: authedUser?.lastName,
|
||||
username: authedUser?.username,
|
||||
},
|
||||
wishlists: wishlistsData.wishlists,
|
||||
collections: collectionsData.collections,
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Not Authed');
|
||||
|
||||
return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] };
|
||||
};
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { dev } from '$app/environment';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const csr = dev;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export const prerender = true;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export const prerender = true;
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export async function load(event) {
|
||||
const { url, locals } = event;
|
||||
const { user, session } = await locals.getAuthedUser();
|
||||
|
||||
return {
|
||||
url: url.pathname,
|
||||
user: locals.user,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import type { PageServerLoad } from './$types';
|
|||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
if (authedUser) {
|
||||
if (user) {
|
||||
console.log('user already signed in');
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
|
|
@ -28,9 +28,9 @@ export const actions: Actions = {
|
|||
default: async (event) => {
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
if (authedUser) {
|
||||
if (user) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
|
@ -38,8 +38,10 @@ export const actions: Actions = {
|
|||
const form = await superValidate(event, zod(signinUsernameDto));
|
||||
|
||||
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse);
|
||||
console.log('Login error', error);
|
||||
if (error) {
|
||||
return setError(form, 'username', error);
|
||||
console.log('Setting error');
|
||||
return setError(form, 'username', 'An error occurred while logging in.');
|
||||
}
|
||||
|
||||
if (!form.valid) {
|
||||
|
|
|
|||
|
|
@ -6,149 +6,59 @@ import { setError, superValidate } from 'sveltekit-superforms/server';
|
|||
import type { PageServerLoad } from './$types';
|
||||
|
||||
const signUpDefaults = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
terms: true,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
terms: true,
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event;
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
if (authedUser) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
if (user) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
// if (userFullyAuthenticated(user, session)) {
|
||||
// const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
// throw redirect('/', message, event);
|
||||
// } else if (userNotFullyAuthenticated(user, session)) {
|
||||
// try {
|
||||
// await lucia.invalidateSession(locals.session!.id!);
|
||||
// } catch (error) {
|
||||
// console.log('Session already invalidated');
|
||||
// }
|
||||
// const sessionCookie = lucia.createBlankSessionCookie();
|
||||
// cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
// path: '.',
|
||||
// ...sessionCookie.attributes,
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
signupForm: await superValidate(zod(signupUsernameEmailDto), {
|
||||
defaults: signUpDefaults,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
signupForm: await superValidate(zod(signupUsernameEmailDto), {
|
||||
defaults: signUpDefaults,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const { locals } = event;
|
||||
default: async (event) => {
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
if (authedUser) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
if (user) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
const form = await superValidate(event, zod(signupUsernameEmailDto));
|
||||
const form = await superValidate(event, zod(signupUsernameEmailDto));
|
||||
|
||||
const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse);
|
||||
if (error) return setError(form, 'username', error);
|
||||
|
||||
if (!form.valid) {
|
||||
const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse);
|
||||
if (error) {
|
||||
form.data.password = '';
|
||||
form.data.confirm_password = '';
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
return setError(form, 'username', 'Unable to log in.');
|
||||
}
|
||||
|
||||
// let session;
|
||||
// let sessionCookie;
|
||||
// // Adding user to the db
|
||||
// console.log('Check if user already exists');
|
||||
//
|
||||
// const existing_user = await db.query.usersTable.findFirst({
|
||||
// where: eq(usersTable.username, form.data.username),
|
||||
// });
|
||||
//
|
||||
// if (existing_user) {
|
||||
// return setError(form, 'username', 'You cannot create an account with that username');
|
||||
// }
|
||||
//
|
||||
// console.log('Creating user');
|
||||
//
|
||||
// const hashedPassword = await new Argon2id().hash(form.data.password);
|
||||
//
|
||||
// const user = await db
|
||||
// .insert(usersTable)
|
||||
// .values({
|
||||
// username: form.data.username,
|
||||
// hashed_password: hashedPassword,
|
||||
// email: form.data.email,
|
||||
// first_name: form.data.firstName ?? '',
|
||||
// last_name: form.data.lastName ?? '',
|
||||
// verified: false,
|
||||
// receive_email: false,
|
||||
// theme: 'system',
|
||||
// })
|
||||
// .returning();
|
||||
// console.log('signup user', user);
|
||||
//
|
||||
// if (!user || user.length === 0) {
|
||||
// return fail(400, {
|
||||
// form,
|
||||
// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// await add_user_to_role(user[0].id, 'user', true);
|
||||
// await db.insert(collections).values({
|
||||
// user_id: user[0].id,
|
||||
// });
|
||||
// await db.insert(wishlistsTable).values({
|
||||
// user_id: user[0].id,
|
||||
// });
|
||||
//
|
||||
// try {
|
||||
// session = await lucia.createSession(user[0].id, {
|
||||
// ip_country: event.locals.ip,
|
||||
// ip_address: event.locals.country,
|
||||
// twoFactorAuthEnabled: false,
|
||||
// isTwoFactorAuthenticated: false,
|
||||
// });
|
||||
// sessionCookie = lucia.createSessionCookie(session.id);
|
||||
// } catch (e: any) {
|
||||
// if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) {
|
||||
// // key already exists
|
||||
// console.error('Lucia Error: ', e);
|
||||
// }
|
||||
// console.log(e);
|
||||
// const message = {
|
||||
// type: 'error',
|
||||
// message: 'Unable to create your account. Please try again.',
|
||||
// };
|
||||
// form.data.password = '';
|
||||
// form.data.confirm_password = '';
|
||||
// error(500, message);
|
||||
// }
|
||||
//
|
||||
// event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
// path: '.',
|
||||
// ...sessionCookie.attributes,
|
||||
// });
|
||||
if (!form.valid) {
|
||||
form.data.password = '';
|
||||
form.data.confirm_password = '';
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
|
||||
redirect(302, '/');
|
||||
// const message = { type: 'success', message: 'Signed Up!' } as const;
|
||||
// throw flashRedirect(message, event);
|
||||
},
|
||||
redirect(302, '/');
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,19 +13,16 @@ import type { PageServerLoad, RequestEvent } from './$types';
|
|||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
const { session } = await locals.getAuthedUser();
|
||||
if (session === null) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const { data } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse);
|
||||
if (!data) {
|
||||
if (!session?.twoFactorEnabled) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
const { totpCredential } = data;
|
||||
if (!totpCredential) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
if (session.twoFactorVerified) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -38,10 +35,14 @@ export const actions: Actions = {
|
|||
validateTotp: async (event) => {
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
const { session } = await locals.getAuthedUser();
|
||||
if (session === null) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
if (!session.twoFactorEnabled || session.twoFactorVerified) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
const { data: totpData } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse);
|
||||
if (!totpData) {
|
||||
|
|
@ -64,16 +65,24 @@ export const actions: Actions = {
|
|||
return setError(totpForm, 'code', totpVerifyError);
|
||||
}
|
||||
|
||||
console.log('Successfully logged in');
|
||||
return message(totpForm, { type: 'success', message: 'Successfully logged in!' });
|
||||
totpForm.data.code = '';
|
||||
const message = { type: 'success', message: 'Successfully logged in!' } as const;
|
||||
redirect(302, '/', message, event);
|
||||
},
|
||||
validateRecoveryCode: async (event) => {
|
||||
const { cookies, locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
const { session } = await locals.getAuthedUser();
|
||||
if (session === null) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
if (!session?.twoFactorEnabled) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
if (session.twoFactorVerified) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
const { dbUser, twoFactorDetails } = await validateUserData(event, locals);
|
||||
|
||||
|
|
@ -157,7 +166,7 @@ async function validateUserData(event: RequestEvent, locals: App.Locals) {
|
|||
throw fail(401);
|
||||
}
|
||||
|
||||
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
|
||||
const twoFactorVerified = session?.twoFactorVerified;
|
||||
const twoFactorDetails = await db.query.twoFactorTable.findFirst({
|
||||
where: eq(twoFactorTable.userId, dbUser!.id!),
|
||||
});
|
||||
|
|
@ -167,7 +176,7 @@ async function validateUserData(event: RequestEvent, locals: App.Locals) {
|
|||
throw redirect(302, '/login', message, event);
|
||||
}
|
||||
|
||||
if (isTwoFactorAuthenticated && twoFactorDetails.enabled && twoFactorDetails.secret !== '') {
|
||||
if (twoFactorVerified && twoFactorDetails.enabled && twoFactorDetails.secret !== '') {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { loadFlash } from 'sveltekit-flash-message/server';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: LayoutServerLoad = loadFlash(async (event) => {
|
||||
const { locals, url } = event;
|
||||
const user = await locals.getAuthedUser();
|
||||
const { user } = await locals.getAuthedUser();
|
||||
|
||||
return {
|
||||
url: url.pathname,
|
||||
user,
|
||||
|
|
|
|||
Loading…
Reference in a new issue