Merge pull request #20 from BradNut/development

Development
This commit is contained in:
Bradley Shellnut 2024-07-15 11:13:55 -07:00 committed by GitHub
commit 4dd211d3cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 251 additions and 201 deletions

1
.node-version Normal file
View file

@ -0,0 +1 @@
20.6.1

View file

@ -32,23 +32,23 @@
"@types/cookie": "^0.6.0",
"@types/node": "^20.14.10",
"@types/pg": "^8.11.6",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.16.0",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.23.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.41.0",
"eslint-plugin-svelte": "^2.42.0",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.39",
"postcss-import": "^16.1.0",
"postcss-load-config": "^5.1.0",
"postcss-preset-env": "^9.6.0",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.5",
"sass": "^1.77.8",
"satori": "^0.10.13",
"satori": "^0.10.14",
"satori-html": "^0.3.2",
"svelte": "5.0.0-next.175",
"svelte-check": "^3.8.4",
@ -100,7 +100,7 @@
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.2.0",
"lucide-svelte": "^0.407.0",
"lucide-svelte": "^0.408.0",
"open-props": "^1.7.5",
"oslo": "^1.2.1",
"pg": "^8.12.0",

View file

@ -87,8 +87,8 @@ importers:
specifier: 3.2.0
version: 3.2.0
lucide-svelte:
specifier: ^0.407.0
version: 0.407.0(svelte@5.0.0-next.175)
specifier: ^0.408.0
version: 0.408.0(svelte@5.0.0-next.175)
open-props:
specifier: ^1.7.5
version: 1.7.5
@ -160,11 +160,11 @@ importers:
specifier: ^8.11.6
version: 8.11.6
'@typescript-eslint/eslint-plugin':
specifier: ^7.13.0
version: 7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)
specifier: ^7.16.0
version: 7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser':
specifier: ^7.13.0
version: 7.13.0(eslint@8.57.0)(typescript@5.5.3)
specifier: ^7.16.0
version: 7.16.0(eslint@8.57.0)(typescript@5.5.3)
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.39)
@ -178,8 +178,8 @@ importers:
specifier: ^9.1.0
version: 9.1.0(eslint@8.57.0)
eslint-plugin-svelte:
specifier: ^2.41.0
version: 2.41.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
specifier: ^2.42.0
version: 2.42.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
just-clone:
specifier: ^6.2.0
version: 6.2.0
@ -199,17 +199,17 @@ importers:
specifier: ^9.6.0
version: 9.6.0(postcss@8.4.39)
prettier:
specifier: ^3.3.2
version: 3.3.2
specifier: ^3.3.3
version: 3.3.3
prettier-plugin-svelte:
specifier: ^3.2.5
version: 3.2.5(prettier@3.3.2)(svelte@5.0.0-next.175)
version: 3.2.5(prettier@3.3.3)(svelte@5.0.0-next.175)
sass:
specifier: ^1.77.8
version: 1.77.8
satori:
specifier: ^0.10.13
version: 0.10.13
specifier: ^0.10.14
version: 0.10.14
satori-html:
specifier: ^0.3.2
version: 0.3.2
@ -1923,8 +1923,8 @@ packages:
'@types/validator@13.12.0':
resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==}
'@typescript-eslint/eslint-plugin@7.13.0':
resolution: {integrity: sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==}
'@typescript-eslint/eslint-plugin@7.16.0':
resolution: {integrity: sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
@ -1934,8 +1934,8 @@ packages:
typescript:
optional: true
'@typescript-eslint/parser@7.13.0':
resolution: {integrity: sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==}
'@typescript-eslint/parser@7.16.0':
resolution: {integrity: sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
@ -1944,12 +1944,12 @@ packages:
typescript:
optional: true
'@typescript-eslint/scope-manager@7.13.0':
resolution: {integrity: sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==}
'@typescript-eslint/scope-manager@7.16.0':
resolution: {integrity: sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==}
engines: {node: ^18.18.0 || >=20.0.0}
'@typescript-eslint/type-utils@7.13.0':
resolution: {integrity: sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==}
'@typescript-eslint/type-utils@7.16.0':
resolution: {integrity: sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
@ -1958,12 +1958,12 @@ packages:
typescript:
optional: true
'@typescript-eslint/types@7.13.0':
resolution: {integrity: sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==}
'@typescript-eslint/types@7.16.0':
resolution: {integrity: sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==}
engines: {node: ^18.18.0 || >=20.0.0}
'@typescript-eslint/typescript-estree@7.13.0':
resolution: {integrity: sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==}
'@typescript-eslint/typescript-estree@7.16.0':
resolution: {integrity: sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
@ -1971,14 +1971,14 @@ packages:
typescript:
optional: true
'@typescript-eslint/utils@7.13.0':
resolution: {integrity: sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==}
'@typescript-eslint/utils@7.16.0':
resolution: {integrity: sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
'@typescript-eslint/visitor-keys@7.13.0':
resolution: {integrity: sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==}
'@typescript-eslint/visitor-keys@7.16.0':
resolution: {integrity: sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==}
engines: {node: ^18.18.0 || >=20.0.0}
'@ungap/structured-clone@1.2.0':
@ -2592,12 +2592,12 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
eslint-plugin-svelte@2.41.0:
resolution: {integrity: sha512-gjU9Q/psxbWG1VNwYbEb0Q6U4W5PBGaDpYmO2zlQ+zlAMVS3Qt0luAK0ACi/tMSwRK6JENiySvMyJbO0YWmXSg==}
eslint-plugin-svelte@2.42.0:
resolution: {integrity: sha512-mHP6z0DWq97KZvoQcApZHdF9m9epcDV/ICKufeEH18Vh+8vl7S+gwt8WdUohEqKNVMuXRkbvy1suMcVvUDiOGw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.155
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.181
peerDependenciesMeta:
svelte:
optional: true
@ -3016,8 +3016,8 @@ packages:
lucia@3.2.0:
resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==}
lucide-svelte@0.407.0:
resolution: {integrity: sha512-gtFkn8/+4XedQh1WMm6FVed7v1yny4q8IjjIlhlhZZ5syAUVFrsnuPPuzCgmb0zNSo/fvn4RnytO/DX4iP3iUQ==}
lucide-svelte@0.408.0:
resolution: {integrity: sha512-5rJvcnHvE+K/2ebff/tKvG1FmIut01hDUaibMk6pG2Je+82TCPflVvhMvgCjP0fkM0ztj5t/ma1s5WYyketOSA==}
peerDependencies:
svelte: ^3 || ^4 || ^5.0.0-next.42
@ -3078,6 +3078,10 @@ packages:
resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
engines: {node: '>=16 || 14 >=14.17'}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@ -3647,8 +3651,8 @@ packages:
prettier: ^3.0.0
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
prettier@3.3.2:
resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==}
prettier@3.3.3:
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
hasBin: true
@ -3762,8 +3766,8 @@ packages:
satori-html@0.3.2:
resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==}
satori@0.10.13:
resolution: {integrity: sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ==}
satori@0.10.14:
resolution: {integrity: sha512-abovcqmwl97WKioxpkfuMeZmndB1TuDFY/R+FymrZyiGP+pMYomvgSzVPnbNMWHHESOPosVHGL352oFbdAnJcA==}
engines: {node: '>=16'}
satori@0.10.9:
@ -3911,11 +3915,11 @@ packages:
peerDependencies:
svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0
svelte-eslint-parser@0.39.2:
resolution: {integrity: sha512-87UwLuWTtDIuzWOhOi1zBL5wYVd07M5BK1qZ57YmXJB5/UmjUNJqGy3XSOhPqjckY1dATNV9y+mx+nI0WH6HPA==}
svelte-eslint-parser@0.40.0:
resolution: {integrity: sha512-M+v1HhC5T1WKYVxWexUCS4o6oIBS88XKzOZuhl2ew+eGxol7eC21e+VE8TC4rXJ3iT3iXT0qlZsZcpKjVo5/zQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.115
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.181
peerDependenciesMeta:
svelte:
optional: true
@ -5705,14 +5709,14 @@ snapshots:
'@types/validator@13.12.0':
optional: true
'@typescript-eslint/eslint-plugin@7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)':
'@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.13.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/scope-manager': 7.13.0
'@typescript-eslint/type-utils': 7.13.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/utils': 7.13.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.13.0
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.16.0
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
@ -5723,12 +5727,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.5.3)':
'@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.13.0
'@typescript-eslint/types': 7.13.0
'@typescript-eslint/typescript-estree': 7.13.0(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.13.0
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.16.0
debug: 4.3.4
eslint: 8.57.0
optionalDependencies:
@ -5736,15 +5740,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@7.13.0':
'@typescript-eslint/scope-manager@7.16.0':
dependencies:
'@typescript-eslint/types': 7.13.0
'@typescript-eslint/visitor-keys': 7.13.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/visitor-keys': 7.16.0
'@typescript-eslint/type-utils@7.13.0(eslint@8.57.0)(typescript@5.5.3)':
'@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@typescript-eslint/typescript-estree': 7.13.0(typescript@5.5.3)
'@typescript-eslint/utils': 7.13.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
debug: 4.3.5
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.3)
@ -5753,16 +5757,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@7.13.0': {}
'@typescript-eslint/types@7.16.0': {}
'@typescript-eslint/typescript-estree@7.13.0(typescript@5.5.3)':
'@typescript-eslint/typescript-estree@7.16.0(typescript@5.5.3)':
dependencies:
'@typescript-eslint/types': 7.13.0
'@typescript-eslint/visitor-keys': 7.13.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/visitor-keys': 7.16.0
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
minimatch: 9.0.5
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
optionalDependencies:
@ -5770,20 +5774,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@7.13.0(eslint@8.57.0)(typescript@5.5.3)':
'@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@typescript-eslint/scope-manager': 7.13.0
'@typescript-eslint/types': 7.13.0
'@typescript-eslint/typescript-estree': 7.13.0(typescript@5.5.3)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
eslint: 8.57.0
transitivePeerDependencies:
- supports-color
- typescript
'@typescript-eslint/visitor-keys@7.13.0':
'@typescript-eslint/visitor-keys@7.16.0':
dependencies:
'@typescript-eslint/types': 7.13.0
'@typescript-eslint/types': 7.16.0
eslint-visitor-keys: 3.4.3
'@ungap/structured-clone@1.2.0': {}
@ -6380,7 +6384,7 @@ snapshots:
dependencies:
eslint: 8.57.0
eslint-plugin-svelte@2.41.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
eslint-plugin-svelte@2.42.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@jridgewell/sourcemap-codec': 1.4.15
@ -6393,7 +6397,7 @@ snapshots:
postcss-safe-parser: 6.0.0(postcss@8.4.39)
postcss-selector-parser: 6.1.0
semver: 7.6.2
svelte-eslint-parser: 0.39.2(svelte@5.0.0-next.175)
svelte-eslint-parser: 0.40.0(svelte@5.0.0-next.175)
optionalDependencies:
svelte: 5.0.0-next.175
transitivePeerDependencies:
@ -6851,7 +6855,7 @@ snapshots:
dependencies:
oslo: 1.2.0
lucide-svelte@0.407.0(svelte@5.0.0-next.175):
lucide-svelte@0.408.0(svelte@5.0.0-next.175):
dependencies:
svelte: 5.0.0-next.175
@ -6907,6 +6911,10 @@ snapshots:
dependencies:
brace-expansion: 2.0.1
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minimist@1.2.8: {}
minipass@3.3.6:
@ -7462,12 +7470,12 @@ snapshots:
prelude-ls@1.2.1: {}
prettier-plugin-svelte@3.2.5(prettier@3.3.2)(svelte@5.0.0-next.175):
prettier-plugin-svelte@3.2.5(prettier@3.3.3)(svelte@5.0.0-next.175):
dependencies:
prettier: 3.3.2
prettier: 3.3.3
svelte: 5.0.0-next.175
prettier@3.3.2: {}
prettier@3.3.3: {}
pretty-format@29.7.0:
dependencies:
@ -7614,7 +7622,7 @@ snapshots:
dependencies:
ultrahtml: 1.5.3
satori@0.10.13:
satori@0.10.14:
dependencies:
'@shuding/opentype.js': 1.4.0-beta.0
css-background-parser: 0.1.0
@ -7803,7 +7811,7 @@ snapshots:
- stylus
- sugarss
svelte-eslint-parser@0.39.2(svelte@5.0.0-next.175):
svelte-eslint-parser@0.40.0(svelte@5.0.0-next.175):
dependencies:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3

View file

@ -34,6 +34,7 @@ for (const table of [
schema.sessions,
schema.userRoles,
schema.users,
schema.twoFactor,
schema.wishlists,
schema.wishlist_items,
]) {

View file

@ -13,8 +13,7 @@ import { lucia } from '$lib/server/auth';
// });
export const authentication: Handle = async function ({ event, resolve }) {
const startTimer = Date.now();
event.locals.startTimer = startTimer;
event.locals.startTimer = Date.now();
const ip = event.request.headers.get('x-forwarded-for') as string;
const country = event.request.headers.get('x-vercel-ip-country') as string;

View file

@ -39,7 +39,7 @@ export const lucia = new Lucia(adapter, {
expires: false, // session cookies have very long lifespan (2 years)
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production',
secure: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production',
sameSite: 'strict',
domain,
},

View file

@ -25,5 +25,9 @@ export const signInSchema = z.object({
});
export const totpSchema = z.object({
totpToken: z.string().trim().min(6).max(10),
totpToken: z.string().trim().min(6).max(6),
});
export const recoveryCodeSchema = z.object({
recoveryCode: z.string().trim().min(10).max(10),
});

View file

@ -1,5 +1,5 @@
import { fail, error, type Actions } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { eq, or } from 'drizzle-orm';
import { Argon2id } from 'oslo/password';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
@ -58,7 +58,7 @@ export const actions: Actions = {
let session;
let sessionCookie;
const user: Users | undefined = await db.query.users.findFirst({
where: eq(users.username, form.data.username),
where: or(eq(users.username, form.data.username), eq(users.email, form.data.username)),
});
if (!user) {

View file

@ -41,15 +41,29 @@
</svelte:head>
<div class="login">
<form method="POST" use:enhance>
<h2
<h2
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
>
Log into your account
</h2>
>
Log into your account
</h2>
{@render usernamePasswordForm()}
<p class="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our
<a href="/terms" class="underline underline-offset-4 hover:text-primary">
Terms of Use
</a>
and
<a href="/privacy" class="underline underline-offset-4 hover:text-primary">
Privacy Policy
</a>.
</p>
</div>
{#snippet usernamePasswordForm()}
<form method="POST" use:enhance>
<Form.Field form={superLoginForm} name="username">
<Form.Control let:attrs>
<Form.Label for="username">Username</Form.Label>
<Form.Label for="username">Username/Email</Form.Label>
<Input {...attrs} autocomplete="username" bind:value={$loginForm.username} />
</Form.Control>
<Form.FieldErrors />
@ -62,22 +76,13 @@
<Form.FieldErrors />
</Form.Field>
<Form.Button>Login</Form.Button>
<p class="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our
<a href="/terms" class="underline underline-offset-4 hover:text-primary">
Terms of Use
</a>
and
<a href="/privacy" class="underline underline-offset-4 hover:text-primary">
Privacy Policy
</a>.
</p>
</form>
</div>
{/snippet}
<style lang="postcss">
.login {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
flex-direction: column;
justify-content: center;

View file

@ -12,6 +12,7 @@ import { add_user_to_role } from '$server/roles';
import db from '../../../db';
import { collections, users, wishlists } from '$db/schema';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
const limiter = new RateLimiter({
// A rate is defined by [number, unit]
@ -29,16 +30,23 @@ const signUpDefaults = {
};
export const load: PageServerLoad = async (event) => {
// redirect(
// 302,
// '/waitlist',
// { type: 'error', message: 'Sign-up not yet available. Please add your email to the waitlist!' },
// event
// );
const { locals, cookies } = event;
const { user, session } = event.locals;
if (event.locals.user) {
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 {
@ -91,8 +99,6 @@ export const actions: Actions = {
verified: false,
receive_email: false,
theme: 'system',
two_factor_secret: '',
two_factor_enabled: false,
})
.returning();
console.log('signup user', user);
@ -104,7 +110,7 @@ export const actions: Actions = {
});
}
add_user_to_role(user[0].id, 'user', true);
await add_user_to_role(user[0].id, 'user', true);
await db.insert(collections).values({
user_id: user[0].id,
});

View file

@ -1,4 +1,4 @@
import { fail, error, type Actions, type Cookies, type RequestEvent } from '@sveltejs/kit';
import { fail, error, type Actions } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm';
import { Argon2id } from 'oslo/password';
import { decodeHex } from 'oslo/encoding';
@ -9,7 +9,7 @@ import { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '../../../db';
import { lucia } from '$lib/server/auth';
import { totpSchema } from '$lib/validations/auth';
import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth';
import { users, twoFactor, recoveryCodes } from '$db/schema';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
@ -33,7 +33,10 @@ export const load: PageServerLoad = async (event) => {
});
if (!twoFactorDetails || !twoFactorDetails.enabled) {
const message = { type: 'error', message: 'Two factor authentication is not enabled' } as const;
const message = {
type: 'error',
message: 'Two factor authentication is not enabled',
} as const;
redirect(302, '/login', message, event);
}
@ -49,10 +52,13 @@ export const load: PageServerLoad = async (event) => {
}
// Check if two factor started less than TWO_FACTOR_TIMEOUT
const timeElapsed = Date.now() - twoFactorInitiatedTime.getTime();
console.log('Time elapsed', timeElapsed);
if (timeElapsed > env.TWO_FACTOR_TIMEOUT) {
console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', timeElapsed, env.TWO_FACTOR_TIMEOUT);
const totpElapsed = totpTimeElapsed(twoFactorInitiatedTime);
if (totpElapsed) {
console.log(
'Time elapsed was more than TWO_FACTOR_TIMEOUT',
totpElapsed,
env.TWO_FACTOR_TIMEOUT,
);
await lucia.invalidateSession(session!.id!);
const sessionCookie = lucia.createBlankSessionCookie();
cookies.set(sessionCookie.name, sessionCookie.value, {
@ -68,20 +74,15 @@ export const load: PageServerLoad = async (event) => {
console.log('session', session);
console.log('isTwoFactorAuthenticated', isTwoFactorAuthenticated);
if (
isTwoFactorAuthenticated &&
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== ''
) {
if (isTwoFactorAuthenticated && twoFactorDetails?.enabled && twoFactorDetails?.secret !== '') {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
}
}
const form = await superValidate(event, zod(totpSchema));
return {
form,
totpForm: await superValidate(event, zod(totpSchema)),
recoveryCodeForm: await superValidate(event, zod(recoveryCodeSchema)),
};
};
@ -115,13 +116,14 @@ export const actions: Actions = {
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id!),
})
});
if (
isTwoFactorAuthenticated &&
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== ''
) {
if (!twoFactorDetails) {
const message = { type: 'error', message: 'Unable to process request' } as const;
throw redirect(302, '/login', message, event);
}
if (isTwoFactorAuthenticated && twoFactorDetails.enabled && twoFactorDetails.secret !== '') {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
}
@ -140,19 +142,32 @@ export const actions: Actions = {
const totpToken = form?.data?.totpToken;
const twoFactorSecretPopulated =
twoFactorDetails?.secret !== '' && twoFactorDetails?.secret !== null;
if (twoFactorDetails?.enabled && !twoFactorSecretPopulated && !totpToken) {
twoFactorDetails.secret !== '' && twoFactorDetails.secret !== null;
if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) {
return fail(400, {
form,
});
} else if (twoFactorSecretPopulated && totpToken) {
// Check if two factor started less than TWO_FACTOR_TIMEOUT
await checkTOTPExpiry(twoFactorDetails, session, cookies, event);
const totpElapsed = totpTimeElapsed(twoFactorDetails.initiatedTime ?? new Date());
if (totpElapsed) {
await lucia.invalidateSession(session!.id!);
const sessionCookie = lucia.createBlankSessionCookie();
cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes,
});
const message = {
type: 'error',
message: 'Two factor authentication has expired',
} as const;
redirect(302, '/login', message, event);
}
console.log('totpToken', totpToken);
const validOTP = await new TOTPController().verify(
totpToken,
decodeHex(twoFactorDetails?.secret ?? ''),
decodeHex(twoFactorDetails.secret ?? ''),
);
console.log('validOTP', validOTP);
@ -161,6 +176,7 @@ export const actions: Actions = {
const usedRecoveryCode = await checkRecoveryCode(totpToken, dbUser.id);
if (!usedRecoveryCode) {
console.log('invalid TOTP code');
form.data.totpToken = '';
return setError(form, 'totpToken', 'Invalid code.');
}
}
@ -195,24 +211,22 @@ export const actions: Actions = {
},
};
async function checkTOTPExpiry(twoFactorDetails: { id: string; cuid: string | null; secret: string; enabled: boolean; initiatedTime: Date | null; createdAt: Date; updatedAt: Date; userId: string; } | undefined, session, cookies: Cookies, event: RequestEvent<Partial<Record<string, string>>, string | null>) {
const twoFactorInitiatedTime = twoFactorDetails?.initiatedTime;
if (twoFactorInitiatedTime === null || twoFactorInitiatedTime === undefined) {
redirect(302, '/login');
function totpTimeElapsed(initiatedTime: Date) {
if (initiatedTime === null || initiatedTime === undefined) {
return true;
}
const timeElapsed = Date.now() - twoFactorInitiatedTime.getTime();
const timeElapsed = Date.now() - initiatedTime.getTime();
console.log('Time elapsed', timeElapsed);
if (timeElapsed > env.TWO_FACTOR_TIMEOUT) {
console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', timeElapsed, env.TWO_FACTOR_TIMEOUT);
await lucia.invalidateSession(session!.id!);
const sessionCookie = lucia.createBlankSessionCookie();
cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes,
});
const message = { type: 'error', message: 'Two factor authentication has expired' } as const;
redirect(302, '/login', message, event);
console.log(
'Time elapsed was more than TWO_FACTOR_TIMEOUT',
timeElapsed,
env.TWO_FACTOR_TIMEOUT,
);
return true;
}
return false;
}
async function checkRecoveryCode(recoveryCode: string, userId: string) {

View file

@ -3,20 +3,17 @@
import { superForm } from 'sveltekit-superforms/client';
import * as flashModule from 'sveltekit-flash-message/client';
import { AlertCircle } from "lucide-svelte";
import { signInSchema, totpSchema } from '$lib/validations/auth';
import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth';
import * as Form from '$lib/components/ui/form';
import { Label } from '$components/ui/label';
import { Input } from '$components/ui/input';
import { Button } from '$components/ui/button';
import * as Alert from "$components/ui/alert";
import { boredState } from '$lib/stores/boredState.js';
import PinInput from '$components/pin-input.svelte';
const { data } = $props();
const superTotpForm = superForm(data.form, {
onSubmit: () => boredState.update((n) => ({ ...n, loading: true })),
onResult: () => boredState.update((n) => ({ ...n, loading: false })),
const superTotpForm = superForm(data.totpForm, {
flashMessage: {
module: flashModule,
onError: ({ result, flashMessage }) => {
@ -34,63 +31,81 @@
delayMs: 0,
});
const superRecoveryCodeForm = superForm(data.recoveryCodeForm, {
validators: zodClient(recoveryCodeSchema),
resetForm: false,
flashMessage: {
module: flashModule,
onError: ({ result, flashMessage }) => {
// Error handling for the flash message:
// - result is the ActionResult
// - message is the flash store (not the status message store)
const errorMessage = result.error.message
flashMessage.set({ type: 'error', message: errorMessage });
}
},
syncFlashMessage: false,
taintedMessage: null,
validationMethod: 'oninput',
delayMs: 0,
});
let showRecoveryCode = $state(false);
const { form: totpForm, enhance } = superTotpForm;
const { form: totpFormData, enhance: totpEnhance } = superTotpForm;
const { form: recoveryCodeFormData, enhance: recoveryCodeEnhance } = superRecoveryCodeForm;
</script>
<svelte:head>
<title>Bored Game | Login</title>
</svelte:head>
<div class="login">
<form method="POST" use:enhance>
<h2
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
>
Please enter your {showRecoveryCode ? 'recovery code' : 'TOTP code'}
</h2>
<Form.Field form={superTotpForm} name="totpToken">
<Form.Control let:attrs>
{#if showRecoveryCode}
<Form.Label for="totpToken">Recovery Code</Form.Label>
<Input {...attrs} autocomplete="one-time-code" bind:value={$totpForm.totpToken} />
{:else}
<Form.Label for="totpToken">TOTP Code</Form.Label>
<PinInput {...attrs} bind:value={$totpForm.totpToken} />
{/if}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Button>Submit</Form.Button>
</form>
<div class="totp">
<h2
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
>
Please enter your {showRecoveryCode ? 'recovery code' : 'TOTP code'}
</h2>
{#if !showRecoveryCode}
{@render totpForm()}
<Button variant="link" class="text-secondary-foreground" on:click={() => showRecoveryCode = true}>Show Recovery Code</Button>
{:else}
{@render recoveryCodeForm()}
<Button variant="link" class="text-secondary-foreground" on:click={() => showRecoveryCode = false}>Show TOTP Code</Button>
{/if}
</div>
<style lang="postcss">
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
{#snippet totpForm()}
<form method="POST" use:totpEnhance>
<Form.Field class="form-field-container" form={totpFormData} name="totpToken">
<Form.Control let:attrs>
<Form.Label for="totpToken">TOTP Code</Form.Label>
<PinInput {...attrs} bind:value={$totpFormData.totpToken} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Button class="w-full">Submit</Form.Button>
</form>
{/snippet}
h3 {
color: white;
}
}
.login {
{#snippet recoveryCodeForm()}
<form method="POST" use:recoveryCodeEnhance>
<Form.Field form={recoveryCodeFormData} name="recoveryCode">
<Form.Control let:attrs>
<Form.Label for="totpToken">Recovery Code</Form.Label>
<Input {...attrs} bind:value={$recoveryCodeFormData.recoveryCode} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Button class="w-full">Submit</Form.Button>
</form>
{/snippet}
<style lang="postcss">
.totp {
display: flex;
margin-top: 1.5rem;
flex-direction: column;
justify-content: center;
width: 100%;
margin-right: auto;
margin-left: auto;

View file

@ -18,9 +18,6 @@ export default defineConfig({
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
},
define: {
SUPERFORMS_LEGACY: true
},
css: {
devSourcemap: true,
preprocessorOptions: {