mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Usage of TOTP Code or Recovery code on login. If recovery code then mark that code as used. Setup disabling of 2FA if a current password is entered.
This commit is contained in:
parent
389fddc32e
commit
53f3b99133
11 changed files with 285 additions and 152 deletions
10
package.json
10
package.json
|
|
@ -30,8 +30,8 @@
|
|||
"@sveltejs/kit": "^2.5.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/pg": "^8.11.4",
|
||||
"@types/node": "^20.12.6",
|
||||
"@types/pg": "^8.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
"drizzle-kit": "^0.20.14",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"postcss": "^8.4.38",
|
||||
|
|
@ -54,12 +54,12 @@
|
|||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-headless-table": "^0.18.2",
|
||||
"svelte-meta-tags": "^3.1.1",
|
||||
"svelte-meta-tags": "^3.1.2",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.1",
|
||||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-rate-limiter": "^0.4.3",
|
||||
"sveltekit-superforms": "^2.12.2",
|
||||
"sveltekit-superforms": "^2.12.4",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.1",
|
||||
|
|
|
|||
123
pnpm-lock.yaml
123
pnpm-lock.yaml
|
|
@ -58,13 +58,13 @@ dependencies:
|
|||
version: 0.6.0
|
||||
drizzle-orm:
|
||||
specifier: ^0.30.7
|
||||
version: 0.30.7(@neondatabase/serverless@0.9.0)(@planetscale/database@1.16.0)(@types/pg@8.11.4)(pg@8.11.5)(postgres@3.4.4)
|
||||
version: 0.30.7(@neondatabase/serverless@0.9.0)(@planetscale/database@1.16.0)(@types/pg@8.11.5)(pg@8.11.5)(postgres@3.4.4)
|
||||
feather-icons:
|
||||
specifier: ^4.29.1
|
||||
version: 4.29.1
|
||||
formsnap:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1(svelte@4.2.12)(sveltekit-superforms@2.12.2)
|
||||
version: 0.5.1(svelte@4.2.12)(sveltekit-superforms@2.12.4)
|
||||
html-entities:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
|
|
@ -152,11 +152,11 @@ devDependencies:
|
|||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
'@types/node':
|
||||
specifier: ^20.12.5
|
||||
version: 20.12.5
|
||||
specifier: ^20.12.6
|
||||
version: 20.12.6
|
||||
'@types/pg':
|
||||
specifier: ^8.11.4
|
||||
version: 8.11.4
|
||||
specifier: ^8.11.5
|
||||
version: 8.11.5
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.21.0
|
||||
version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.4)
|
||||
|
|
@ -179,8 +179,8 @@ devDependencies:
|
|||
specifier: ^9.1.0
|
||||
version: 9.1.0(eslint@8.57.0)
|
||||
eslint-plugin-svelte:
|
||||
specifier: ^2.35.1
|
||||
version: 2.35.1(eslint@8.57.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
specifier: ^2.36.0
|
||||
version: 2.36.0(eslint@8.57.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
just-clone:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
|
|
@ -224,8 +224,8 @@ devDependencies:
|
|||
specifier: ^0.18.2
|
||||
version: 0.18.2(svelte@4.2.12)
|
||||
svelte-meta-tags:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1(svelte@4.2.12)(typescript@5.4.4)
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(svelte@4.2.12)(typescript@5.4.4)
|
||||
svelte-preprocess:
|
||||
specifier: ^5.1.3
|
||||
version: 5.1.3(postcss-load-config@5.0.3)(postcss@8.4.38)(sass@1.74.1)(svelte@4.2.12)(typescript@5.4.4)
|
||||
|
|
@ -239,14 +239,14 @@ devDependencies:
|
|||
specifier: ^0.4.3
|
||||
version: 0.4.3(@sveltejs/kit@2.5.5)
|
||||
sveltekit-superforms:
|
||||
specifier: ^2.12.2
|
||||
version: 2.12.2(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12)
|
||||
specifier: ^2.12.4
|
||||
version: 2.12.4(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.3
|
||||
version: 3.4.3(ts-node@10.9.2)
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@20.12.5)(typescript@5.4.4)
|
||||
version: 10.9.2(@types/node@20.12.6)(typescript@5.4.4)
|
||||
tslib:
|
||||
specifier: ^2.6.1
|
||||
version: 2.6.2
|
||||
|
|
@ -258,10 +258,10 @@ devDependencies:
|
|||
version: 5.4.4
|
||||
vite:
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
version: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
vitest:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(@types/node@20.12.5)(sass@1.74.1)
|
||||
version: 1.4.0(@types/node@20.12.6)(sass@1.74.1)
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
|
|
@ -3286,7 +3286,7 @@ packages:
|
|||
sirv: 2.0.4
|
||||
svelte: 4.2.12
|
||||
tiny-glob: 0.2.9
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
|
||||
/@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.2.8):
|
||||
resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==}
|
||||
|
|
@ -3299,7 +3299,7 @@ packages:
|
|||
'@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.2.8)
|
||||
debug: 4.3.4
|
||||
svelte: 4.2.12
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -3317,7 +3317,7 @@ packages:
|
|||
magic-string: 0.30.5
|
||||
svelte: 4.2.12
|
||||
svelte-hmr: 0.15.3(svelte@4.2.12)
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
vitefu: 0.2.5(vite@5.2.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -3360,22 +3360,22 @@ packages:
|
|||
/@types/json-schema@7.0.15:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
/@types/node@20.12.5:
|
||||
resolution: {integrity: sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==}
|
||||
/@types/node@20.12.6:
|
||||
resolution: {integrity: sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/pg@8.11.4:
|
||||
resolution: {integrity: sha512-yw3Bwbda6vO+NvI1Ue/YKOwtl31AYvvd/e73O3V4ZkNzuGpTDndLSyc0dQRB2xrQqDePd20pEGIfqSp/GH3pRw==}
|
||||
/@types/pg@8.11.5:
|
||||
resolution: {integrity: sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==}
|
||||
dependencies:
|
||||
'@types/node': 20.12.5
|
||||
'@types/node': 20.12.6
|
||||
pg-protocol: 1.6.0
|
||||
pg-types: 4.0.2
|
||||
|
||||
/@types/pg@8.6.6:
|
||||
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
|
||||
dependencies:
|
||||
'@types/node': 20.12.5
|
||||
'@types/node': 20.12.6
|
||||
pg-protocol: 1.6.0
|
||||
pg-types: 2.2.0
|
||||
dev: false
|
||||
|
|
@ -4312,7 +4312,7 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/drizzle-orm@0.30.7(@neondatabase/serverless@0.9.0)(@planetscale/database@1.16.0)(@types/pg@8.11.4)(pg@8.11.5)(postgres@3.4.4):
|
||||
/drizzle-orm@0.30.7(@neondatabase/serverless@0.9.0)(@planetscale/database@1.16.0)(@types/pg@8.11.5)(pg@8.11.5)(postgres@3.4.4):
|
||||
resolution: {integrity: sha512-9qefSZQlu2fO2qv24piHyWFWcxcOY15//0v4j8qomMqaxzipNoG+fUBrQ7Ftk7PY7APRbRdn/nkEXWxiI4a8mw==}
|
||||
peerDependencies:
|
||||
'@aws-sdk/client-rds-data': '>=3'
|
||||
|
|
@ -4394,7 +4394,7 @@ packages:
|
|||
dependencies:
|
||||
'@neondatabase/serverless': 0.9.0
|
||||
'@planetscale/database': 1.16.0
|
||||
'@types/pg': 8.11.4
|
||||
'@types/pg': 8.11.5
|
||||
pg: 8.11.5
|
||||
postgres: 3.4.4
|
||||
dev: false
|
||||
|
|
@ -4613,13 +4613,14 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/eslint-compat-utils@0.1.2(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==}
|
||||
/eslint-compat-utils@0.5.0(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
eslint: '>=6.0.0'
|
||||
dependencies:
|
||||
eslint: 8.57.0
|
||||
semver: 7.6.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-prettier@9.1.0(eslint@8.57.0):
|
||||
|
|
@ -4631,12 +4632,12 @@ packages:
|
|||
eslint: 8.57.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-svelte@2.35.1(eslint@8.57.0)(svelte@4.2.12)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==}
|
||||
/eslint-plugin-svelte@2.36.0(eslint@8.57.0)(svelte@4.2.12)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-D30hSj13Y8YEn7yGXos7EYp0lpEb3Z2V/M+6a3MZ13KGVhaefdW2A9j8IBIcW4YR+j6fo901USzLeXQz/XbWeQ==}
|
||||
engines: {node: ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^7.0.0 || ^8.0.0-0
|
||||
svelte: ^3.37.0 || ^4.0.0
|
||||
eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0
|
||||
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.95
|
||||
peerDependenciesMeta:
|
||||
svelte:
|
||||
optional: true
|
||||
|
|
@ -4645,16 +4646,16 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
debug: 4.3.4
|
||||
eslint: 8.57.0
|
||||
eslint-compat-utils: 0.1.2(eslint@8.57.0)
|
||||
eslint-compat-utils: 0.5.0(eslint@8.57.0)
|
||||
esutils: 2.0.3
|
||||
known-css-properties: 0.29.0
|
||||
known-css-properties: 0.30.0
|
||||
postcss: 8.4.38
|
||||
postcss-load-config: 3.1.4(postcss@8.4.38)(ts-node@10.9.2)
|
||||
postcss-safe-parser: 6.0.0(postcss@8.4.38)
|
||||
postcss-selector-parser: 6.0.13
|
||||
semver: 7.5.4
|
||||
postcss-selector-parser: 6.0.16
|
||||
semver: 7.6.0
|
||||
svelte: 4.2.12
|
||||
svelte-eslint-parser: 0.33.1(svelte@4.2.12)
|
||||
svelte-eslint-parser: 0.34.1(svelte@4.2.12)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
|
@ -4898,7 +4899,7 @@ packages:
|
|||
is-callable: 1.2.7
|
||||
dev: false
|
||||
|
||||
/formsnap@0.5.1(svelte@4.2.12)(sveltekit-superforms@2.12.2):
|
||||
/formsnap@0.5.1(svelte@4.2.12)(sveltekit-superforms@2.12.4):
|
||||
resolution: {integrity: sha512-8ppOlOu7llBEJbV0PzUz/KWh1J8KfiGqwjiyb8emQ2m+/nYXohLBtMcLVpW3XwlMkUbYaIXM+5lhfGjw8xbGJw==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
|
@ -4906,7 +4907,7 @@ packages:
|
|||
dependencies:
|
||||
nanoid: 5.0.6
|
||||
svelte: 4.2.12
|
||||
sveltekit-superforms: 2.12.2(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12)
|
||||
sveltekit-superforms: 2.12.4(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12)
|
||||
dev: false
|
||||
|
||||
/fraction.js@4.3.7:
|
||||
|
|
@ -5399,8 +5400,8 @@ packages:
|
|||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
/known-css-properties@0.29.0:
|
||||
resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==}
|
||||
/known-css-properties@0.30.0:
|
||||
resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==}
|
||||
dev: true
|
||||
|
||||
/levn@0.4.1:
|
||||
|
|
@ -6334,7 +6335,7 @@ packages:
|
|||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
postcss: 8.4.38
|
||||
ts-node: 10.9.2(@types/node@20.12.5)(typescript@5.4.4)
|
||||
ts-node: 10.9.2(@types/node@20.12.6)(typescript@5.4.4)
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
|
|
@ -6352,7 +6353,7 @@ packages:
|
|||
dependencies:
|
||||
lilconfig: 3.0.0
|
||||
postcss: 8.4.38
|
||||
ts-node: 10.9.2(@types/node@20.12.5)(typescript@5.4.4)
|
||||
ts-node: 10.9.2(@types/node@20.12.6)(typescript@5.4.4)
|
||||
yaml: 2.3.4
|
||||
|
||||
/postcss-load-config@5.0.3(postcss@8.4.38):
|
||||
|
|
@ -7163,11 +7164,11 @@ packages:
|
|||
- sugarss
|
||||
dev: true
|
||||
|
||||
/svelte-eslint-parser@0.33.1(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-vo7xPGTlKBGdLH8T5L64FipvTrqv3OQRx9d2z5X05KKZDlF4rQk8KViZO4flKERY+5BiVdOh7zZ7JGJWo5P0uA==}
|
||||
/svelte-eslint-parser@0.34.1(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-9+uLA1pqI9AZioKVGJzYYmlOZWxfoCXSbAM9iaNm7H01XlYlzRTtJfZgl9o3StQGN41PfGJIbkKkfk3e/pHFfA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
svelte: ^3.37.0 || ^4.0.0
|
||||
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.94
|
||||
peerDependenciesMeta:
|
||||
svelte:
|
||||
optional: true
|
||||
|
|
@ -7220,8 +7221,8 @@ packages:
|
|||
resolution: {integrity: sha512-AZD6R60vksyojn21FgXLglmBiBB9K5Dkdu0hdGrLbCaRCYT68IsWkZfRUqKhMx1IfzqWcZQ8X9y/f+Ih0oNQkQ==}
|
||||
dev: false
|
||||
|
||||
/svelte-meta-tags@3.1.1(svelte@4.2.12)(typescript@5.4.4):
|
||||
resolution: {integrity: sha512-tSWU1xbRGV5rkDN4wWXZfY24BRuhPa3Z8W2Zpt3GCv01QQdWRhhgmlpYVdfoSPNQX060bDB/JM1cu4H+lXxe8w==}
|
||||
/svelte-meta-tags@3.1.2(svelte@4.2.12)(typescript@5.4.4):
|
||||
resolution: {integrity: sha512-zw8xSA10ce7atFO1o0N1x41+qU+HBnpGx8KcVRAWPy5iiRdO6fvUFMg6VwJVgMhLSBEUTZXKAvMALLUssbCoCw==}
|
||||
peerDependencies:
|
||||
svelte: ^3.55.0 || ^4.0.0
|
||||
dependencies:
|
||||
|
|
@ -7360,8 +7361,8 @@ packages:
|
|||
'@sveltejs/kit': 2.5.5(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.2.8)
|
||||
dev: true
|
||||
|
||||
/sveltekit-superforms@2.12.2(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-fFOXaluP1os/Tamx7gzwhT3tXPAfqZ8KYRC0UfXdXeUtlUIUfiGrIifDJ26/9uePmF8Zhqy2M0XjG8W9kQnJpg==}
|
||||
/sveltekit-superforms@2.12.4(@sveltejs/kit@2.5.5)(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-0LILJfTpOZj8UhEfcuVZwuyDR63EGTjBq/TwHJn38PcC6KHGZ0bXu8DVHxslGUffEi9EHgeyshoJk7x3EoJVhQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': 1.x || 2.x
|
||||
svelte: 3.x || 4.x || >=5.0.0-next.51
|
||||
|
|
@ -7557,7 +7558,7 @@ packages:
|
|||
/ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
/ts-node@10.9.2(@types/node@20.12.5)(typescript@5.4.4):
|
||||
/ts-node@10.9.2(@types/node@20.12.6)(typescript@5.4.4):
|
||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
|
@ -7576,7 +7577,7 @@ packages:
|
|||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 20.12.5
|
||||
'@types/node': 20.12.6
|
||||
acorn: 8.11.2
|
||||
acorn-walk: 8.3.0
|
||||
arg: 4.1.3
|
||||
|
|
@ -7725,7 +7726,7 @@ packages:
|
|||
- rollup
|
||||
dev: true
|
||||
|
||||
/vite-node@1.4.0(@types/node@20.12.5)(sass@1.74.1):
|
||||
/vite-node@1.4.0(@types/node@20.12.6)(sass@1.74.1):
|
||||
resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
|
@ -7734,7 +7735,7 @@ packages:
|
|||
debug: 4.3.4
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.0.0
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
|
|
@ -7746,7 +7747,7 @@ packages:
|
|||
- terser
|
||||
dev: true
|
||||
|
||||
/vite@5.2.8(@types/node@20.12.5)(sass@1.74.1):
|
||||
/vite@5.2.8(@types/node@20.12.6)(sass@1.74.1):
|
||||
resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
|
@ -7774,7 +7775,7 @@ packages:
|
|||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.12.5
|
||||
'@types/node': 20.12.6
|
||||
esbuild: 0.20.2
|
||||
postcss: 8.4.38
|
||||
rollup: 4.13.0
|
||||
|
|
@ -7790,9 +7791,9 @@ packages:
|
|||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
|
||||
/vitest@1.4.0(@types/node@20.12.5)(sass@1.74.1):
|
||||
/vitest@1.4.0(@types/node@20.12.6)(sass@1.74.1):
|
||||
resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
|
@ -7817,7 +7818,7 @@ packages:
|
|||
jsdom:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.12.5
|
||||
'@types/node': 20.12.6
|
||||
'@vitest/expect': 1.4.0
|
||||
'@vitest/runner': 1.4.0
|
||||
'@vitest/snapshot': 1.4.0
|
||||
|
|
@ -7835,8 +7836,8 @@ packages:
|
|||
strip-literal: 2.0.0
|
||||
tinybench: 2.6.0
|
||||
tinypool: 0.8.2
|
||||
vite: 5.2.8(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite-node: 1.4.0(@types/node@20.12.5)(sass@1.74.1)
|
||||
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)
|
||||
vite-node: 1.4.0(@types/node@20.12.6)(sass@1.74.1)
|
||||
why-is-node-running: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import { userSchema } from './zod-schemas';
|
|||
export const profileSchema = userSchema.pick({
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
username: true
|
||||
username: true,
|
||||
});
|
||||
|
||||
export const changeEmailSchema = userSchema.pick({
|
||||
email: true
|
||||
email: true,
|
||||
});
|
||||
|
||||
export const changeUserPasswordSchema = z
|
||||
.object({
|
||||
current_password: z.string({ required_error: 'Current Password is required' }),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim()
|
||||
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(),
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
refinePasswords(confirm_password, password, ctx);
|
||||
|
|
@ -25,11 +25,17 @@ export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema;
|
|||
|
||||
export const addTwoFactorSchema = z.object({
|
||||
current_password: z.string({ required_error: 'Current Password is required' }),
|
||||
two_factor_code: z.string({ required_error: 'Two Factor Code is required' }).trim()
|
||||
two_factor_code: z.string({ required_error: 'Two Factor Code is required' }).trim(),
|
||||
});
|
||||
|
||||
export type AddTwoFactorSchema = typeof addTwoFactorSchema;
|
||||
|
||||
export const removeTwoFactorSchema = addTwoFactorSchema.pick({
|
||||
current_password: true,
|
||||
});
|
||||
|
||||
export type RemoveTwoFactorSchema = typeof removeTwoFactorSchema;
|
||||
|
||||
export const updateUserPasswordSchema = userSchema
|
||||
.pick({ password: true, confirm_password: true })
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
|
|
@ -39,7 +45,7 @@ export const updateUserPasswordSchema = userSchema
|
|||
export const refinePasswords = async function (
|
||||
confirm_password: string,
|
||||
password: string,
|
||||
ctx: z.RefinementCtx
|
||||
ctx: z.RefinementCtx,
|
||||
) {
|
||||
comparePasswords(confirm_password, password, ctx);
|
||||
checkPasswordStrength(password, ctx);
|
||||
|
|
@ -48,13 +54,13 @@ export const refinePasswords = async function (
|
|||
const comparePasswords = async function (
|
||||
confirm_password: string,
|
||||
password: string,
|
||||
ctx: z.RefinementCtx
|
||||
ctx: z.RefinementCtx,
|
||||
) {
|
||||
if (confirm_password !== password) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Password and Confirm Password must match',
|
||||
path: ['confirm_password']
|
||||
path: ['confirm_password'],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -107,15 +113,15 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
|
|||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: errorMessage,
|
||||
path: ['password']
|
||||
path: ['password'],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const addRoleSchema = z.object({
|
||||
roles: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: 'You have to select at least one item.'
|
||||
})
|
||||
message: 'You have to select at least one item.',
|
||||
}),
|
||||
});
|
||||
|
||||
export type AddRoleSchema = typeof addRoleSchema;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { refinePasswords } from "./account";
|
||||
import { userSchema } from "./zod-schemas";
|
||||
import {z} from "zod";
|
||||
import { refinePasswords } from './account';
|
||||
import { userSchema } from './zod-schemas';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const signUpSchema = userSchema
|
||||
.pick({
|
||||
|
|
@ -10,7 +10,7 @@ export const signUpSchema = userSchema
|
|||
username: true,
|
||||
password: true,
|
||||
confirm_password: true,
|
||||
terms: true
|
||||
terms: true,
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
refinePasswords(confirm_password, password, ctx);
|
||||
|
|
@ -18,12 +18,10 @@ export const signUpSchema = userSchema
|
|||
|
||||
export const signInSchema = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Username must be at least 3 characters' })
|
||||
.max(50, { message: 'Username must be less than 50 characters' }),
|
||||
password: z
|
||||
.string({ required_error: 'Password is required' })
|
||||
.trim(),
|
||||
totpToken: z.string().trim().min(6).max(6).optional()
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Username must be at least 3 characters' })
|
||||
.max(50, { message: 'Username must be less than 50 characters' }),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
totpToken: z.string().trim().min(6).max(10).optional(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -94,11 +94,13 @@
|
|||
</form>
|
||||
<div class="mt-6">
|
||||
{#if !hasSetupTwoFactor}
|
||||
<p>Two Factor Authentication is: <strong>Disabled</strong></p>
|
||||
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
|
||||
<KeyRound class="mr-2 h-4 w-4" />
|
||||
Setup 2FA
|
||||
</Button>
|
||||
{:else}
|
||||
<p>Two Factor Authentication is: <strong>Enabled</strong></p>
|
||||
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
|
||||
<KeyRound class="mr-2 h-4 w-4" />
|
||||
Disable 2FA
|
||||
|
|
|
|||
|
|
@ -7,21 +7,36 @@ import { HMAC } from 'oslo/crypto';
|
|||
import QRCode from 'qrcode';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { redirect, setFlash } from 'sveltekit-flash-message/server';
|
||||
import type { PageServerLoad } from '../../$types';
|
||||
import { addTwoFactorSchema } from '$lib/validations/account';
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import db from '$lib/drizzle';
|
||||
import { users } from '../../../../../../schema';
|
||||
import { recovery_codes, users } from '../../../../../../schema';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const form = await superValidate(event, zod(addTwoFactorSchema));
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
||||
const user = event.locals.user;
|
||||
|
||||
if (!user) {
|
||||
redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, user.id),
|
||||
});
|
||||
|
||||
if (dbUser?.two_factor_enabled) {
|
||||
return {
|
||||
addTwoFactorForm,
|
||||
removeTwoFactorForm,
|
||||
twoFactorEnabled: true,
|
||||
recoveryCodes: [],
|
||||
totpUri: '',
|
||||
qrCode: '',
|
||||
};
|
||||
}
|
||||
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
|
||||
await db
|
||||
.update(users)
|
||||
|
|
@ -36,12 +51,13 @@ export const load: PageServerLoad = async (event) => {
|
|||
// pass the website's name and the user identifier (e.g. email, username)
|
||||
const totpUri = createTOTPKeyURI(issuer, accountName, twoFactorSecret);
|
||||
|
||||
form.data = {
|
||||
addTwoFactorForm.data = {
|
||||
current_password: '',
|
||||
two_factor_code: '',
|
||||
};
|
||||
return {
|
||||
form,
|
||||
addTwoFactorForm,
|
||||
removeTwoFactorForm,
|
||||
twoFactorEnabled: false,
|
||||
recoveryCodes: [],
|
||||
totpUri,
|
||||
|
|
@ -50,12 +66,12 @@ export const load: PageServerLoad = async (event) => {
|
|||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, zod(addTwoFactorSchema));
|
||||
enableTwoFactor: async (event) => {
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
||||
|
||||
if (!form.valid) {
|
||||
if (!addTwoFactorForm.valid) {
|
||||
return fail(400, {
|
||||
form,
|
||||
addTwoFactorForm,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -74,48 +90,102 @@ export const actions: Actions = {
|
|||
});
|
||||
|
||||
if (!dbUser?.hashed_password) {
|
||||
form.data.current_password = '';
|
||||
form.data.two_factor_code = '';
|
||||
addTwoFactorForm.data.current_password = '';
|
||||
addTwoFactorForm.data.two_factor_code = '';
|
||||
return setError(
|
||||
form,
|
||||
addTwoFactorForm,
|
||||
'Error occurred. Please try again or contact support if you need further help.',
|
||||
);
|
||||
}
|
||||
|
||||
if (dbUser?.two_factor_secret === '' || dbUser?.two_factor_secret === null) {
|
||||
form.data.current_password = '';
|
||||
form.data.two_factor_code = '';
|
||||
addTwoFactorForm.data.current_password = '';
|
||||
addTwoFactorForm.data.two_factor_code = '';
|
||||
return setError(
|
||||
form,
|
||||
addTwoFactorForm,
|
||||
'Error occurred. Please try again or contact support if you need further help.',
|
||||
);
|
||||
}
|
||||
|
||||
const currentPasswordVerified = await new Argon2id().verify(
|
||||
dbUser.hashed_password,
|
||||
form.data.current_password,
|
||||
addTwoFactorForm.data.current_password,
|
||||
);
|
||||
|
||||
if (!currentPasswordVerified) {
|
||||
return setError(form, 'current_password', 'Your password is incorrect');
|
||||
return setError(addTwoFactorForm, 'current_password', 'Your password is incorrect');
|
||||
}
|
||||
|
||||
if (form.data.two_factor_code === '') {
|
||||
return setError(form, 'two_factor_code', 'Please enter a code');
|
||||
if (addTwoFactorForm.data.two_factor_code === '') {
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Please enter a code');
|
||||
}
|
||||
|
||||
const twoFactorCode = form.data.two_factor_code;
|
||||
const twoFactorCode = addTwoFactorForm.data.two_factor_code;
|
||||
const validOTP = await new TOTPController().verify(
|
||||
twoFactorCode,
|
||||
decodeHex(dbUser.two_factor_secret),
|
||||
);
|
||||
|
||||
if (!validOTP) {
|
||||
return setError(form, 'two_factor_code', 'Invalid code');
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code');
|
||||
}
|
||||
|
||||
await db.update(users).set({ two_factor_enabled: true }).where(eq(users.id, user.id));
|
||||
|
||||
redirect(302, '/profile/security/two-factor/recovery-codes');
|
||||
},
|
||||
disableTwoFactor: async (event) => {
|
||||
const { cookies } = event;
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
||||
|
||||
if (!removeTwoFactorForm.valid) {
|
||||
return fail(400, {
|
||||
removeTwoFactorForm,
|
||||
});
|
||||
}
|
||||
|
||||
if (!event.locals.user) {
|
||||
redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
if (!event.locals.session) {
|
||||
return fail(401);
|
||||
}
|
||||
|
||||
const user = event.locals.user;
|
||||
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, user.id),
|
||||
});
|
||||
|
||||
if (!dbUser?.hashed_password) {
|
||||
removeTwoFactorForm.data.current_password = '';
|
||||
return setError(
|
||||
removeTwoFactorForm,
|
||||
'Error occurred. Please try again or contact support if you need further help.',
|
||||
);
|
||||
}
|
||||
|
||||
const currentPasswordVerified = await new Argon2id().verify(
|
||||
dbUser.hashed_password,
|
||||
removeTwoFactorForm.data.current_password,
|
||||
);
|
||||
|
||||
if (!currentPasswordVerified) {
|
||||
return setError(removeTwoFactorForm, 'current_password', 'Your password is incorrect');
|
||||
}
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
.set({ two_factor_enabled: false, two_factor_secret: null })
|
||||
.where(eq(users.id, user.id));
|
||||
await db.delete(recovery_codes).where(eq(recovery_codes.userId, user.id));
|
||||
|
||||
setFlash({ type: 'success', message: 'Two-Factor Authentication has been disabled.' }, cookies);
|
||||
return {
|
||||
removeTwoFactorForm,
|
||||
twoFactorEnabled: false,
|
||||
recoveryCodes: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,44 +5,64 @@
|
|||
import * as Alert from '$components/ui/alert';
|
||||
import * as Form from '$components/ui/form';
|
||||
import { Input } from '$components/ui/input';
|
||||
import { addTwoFactorSchema } from '$lib/validations/account';
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
|
||||
|
||||
export let data;
|
||||
|
||||
const { qrCode, twoFactorEnabled, recoveryCodes } = data;
|
||||
|
||||
const form = superForm(data.form, {
|
||||
const addTwoFactorForm = superForm(data.addTwoFactorForm, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(addTwoFactorSchema),
|
||||
delayMs: 500,
|
||||
multipleSubmits: 'prevent',
|
||||
});
|
||||
|
||||
const removeTwoFactorForm = superForm(data.removeTwoFactorForm, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(removeTwoFactorSchema),
|
||||
delayMs: 500,
|
||||
multipleSubmits: 'prevent',
|
||||
});
|
||||
|
||||
console.log('Two Factor: ', twoFactorEnabled, recoveryCodes);
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
const { form: addTwoFactorFormData, enhance: addTwoFactorEnhance } = addTwoFactorForm;
|
||||
const { form: removeTwoFactorFormData, enhance: removeTwoFactorEnhance } = removeTwoFactorForm;
|
||||
</script>
|
||||
|
||||
<h1>Two-Factor Authentication</h1>
|
||||
|
||||
{#if twoFactorEnabled}
|
||||
<h2>Two-Factor Authentication is <span class="text-green-500">enabled</span></h2>
|
||||
<h2>Currently you have two factor authentication <span class="text-green-500">enabled</span></h2>
|
||||
<p>To disable two factor authentication, please enter your current password.</p>
|
||||
<form method="POST" action="?/disableTwoFactor" use:removeTwoFactorEnhance data-sveltekit-replacestate>
|
||||
<Form.Field form={removeTwoFactorForm} name="current_password">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="password">Current Password</Form.Label>
|
||||
<Input type="password" {...attrs} bind:value={$removeTwoFactorFormData.current_password} />
|
||||
</Form.Control>
|
||||
<Form.Description>Please enter your current password.</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Button>Disable Two Factor Authentication</Form.Button>
|
||||
</form>
|
||||
{:else}
|
||||
<h2>Please scan the following QR Code</h2>
|
||||
<img src={qrCode} alt="QR Code" />
|
||||
<form method="POST" use:enhance data-sveltekit-replacestate>
|
||||
<Form.Field {form} name="two_factor_code">
|
||||
<form method="POST" action="?/enableTwoFactor" use:addTwoFactorEnhance data-sveltekit-replacestate>
|
||||
<Form.Field form={addTwoFactorForm} name="two_factor_code">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="code">Enter Code</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.two_factor_code} />
|
||||
<Input {...attrs} bind:value={$addTwoFactorFormData.two_factor_code} />
|
||||
</Form.Control>
|
||||
<Form.Description>This is the code from your authenticator app.</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="current_password">
|
||||
<Form.Field form={addTwoFactorForm} name="current_password">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="password">Enter Password</Form.Label>
|
||||
<Input type="password" {...attrs} bind:value={$formData.current_password} />
|
||||
<Input type="password" {...attrs} bind:value={$addTwoFactorFormData.current_password} />
|
||||
</Form.Control>
|
||||
<Form.Description>Please enter your current password.</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import db from "$lib/drizzle";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {Argon2id} from "oslo/password";
|
||||
import {alphabet, generateRandomString} from "oslo/crypto";
|
||||
import {redirect} from "sveltekit-flash-message/server";
|
||||
import {notSignedInMessage} from "$lib/flashMessages";
|
||||
import db from '$lib/drizzle';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { Argon2id } from 'oslo/password';
|
||||
import { alphabet, generateRandomString } from 'oslo/crypto';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import type { PageServerLoad } from '../../../$types';
|
||||
import {recovery_codes, users} from "../../../../../../../schema";
|
||||
import { recovery_codes, users } from '../../../../../../../schema';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const user = event.locals.user;
|
||||
|
|
@ -24,12 +24,16 @@ export const load: PageServerLoad = async (event) => {
|
|||
});
|
||||
|
||||
if (recoveryCodes.length === 0) {
|
||||
const recoveryCodes = Array.from({length: 5}, () => generateRandomString(10, alphabet('A-Z', '0-9')));
|
||||
const recoveryCodes = Array.from({ length: 5 }, () =>
|
||||
generateRandomString(10, alphabet('A-Z', '0-9')),
|
||||
);
|
||||
if (recoveryCodes) {
|
||||
for (const code of recoveryCodes) {
|
||||
const hashedCode = await new Argon2id().hash(code);
|
||||
console.log('Inserting recovery code', code, hashedCode);
|
||||
await db.insert(recovery_codes).values({
|
||||
userId: user.id,
|
||||
code: await new Argon2id().hash(code),
|
||||
code: hashedCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -39,8 +43,13 @@ export const load: PageServerLoad = async (event) => {
|
|||
}
|
||||
return {
|
||||
recoveryCodes: [],
|
||||
}
|
||||
};
|
||||
} else {
|
||||
redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event);
|
||||
redirect(
|
||||
302,
|
||||
'/profile',
|
||||
{ message: 'Two-Factor Authentication is not enabled', type: 'error' },
|
||||
event,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { fail, error, type Actions } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { and, eq, ne } from 'drizzle-orm';
|
||||
import { Argon2id } from 'oslo/password';
|
||||
import { decodeHex } from 'oslo/encoding';
|
||||
import { TOTPController } from 'oslo/otp';
|
||||
|
|
@ -10,7 +10,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server';
|
|||
import db from '$lib/drizzle';
|
||||
import { lucia } from '$lib/server/auth';
|
||||
import { signInSchema } from '$lib/validations/auth';
|
||||
import { collections, users, wishlists } from '../../../schema';
|
||||
import { users, recovery_codes } from '../../../schema';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
|
|
@ -70,18 +70,18 @@ export const actions: Actions = {
|
|||
return setError(form, '', 'Your username or password is incorrect.');
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(collections)
|
||||
.values({
|
||||
user_id: user.id,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
await db
|
||||
.insert(wishlists)
|
||||
.values({
|
||||
user_id: user.id,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
// await db
|
||||
// .insert(collections)
|
||||
// .values({
|
||||
// user_id: user.id,
|
||||
// })
|
||||
// .onConflictDoNothing();
|
||||
// await db
|
||||
// .insert(wishlists)
|
||||
// .values({
|
||||
// user_id: user.id,
|
||||
// })
|
||||
// .onConflictDoNothing();
|
||||
|
||||
if (user?.two_factor_enabled && user?.two_factor_secret && !form?.data?.totpToken) {
|
||||
return fail(400, {
|
||||
|
|
@ -92,15 +92,21 @@ export const actions: Actions = {
|
|||
console.log('totpToken', form.data.totpToken);
|
||||
const validOTP = await new TOTPController().verify(
|
||||
form.data.totpToken,
|
||||
decodeHex(user.two_factor_secret)
|
||||
decodeHex(user.two_factor_secret),
|
||||
);
|
||||
console.log('validOTP', validOTP);
|
||||
form.errors.totpToken = ['Invalid TOTP code'];
|
||||
|
||||
if (!validOTP) {
|
||||
return fail(400, {
|
||||
form,
|
||||
twoFactorRequired: true,
|
||||
});
|
||||
console.log('invalid TOTP code check for recovery codes');
|
||||
const usedRecoveryCode = await checkRecoveryCode(form?.data?.totpToken, user.id);
|
||||
if (!usedRecoveryCode) {
|
||||
console.log('invalid TOTP code');
|
||||
form.errors.totpToken = ['Invalid code'];
|
||||
return fail(400, {
|
||||
form,
|
||||
twoFactorRequired: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('ip', locals.ip);
|
||||
|
|
@ -131,3 +137,22 @@ export const actions: Actions = {
|
|||
redirect(302, '/', message, event);
|
||||
},
|
||||
};
|
||||
|
||||
async function checkRecoveryCode(recoveryCode: string, userId: string) {
|
||||
const userRecoveryCodes = await db.query.recovery_codes.findMany({
|
||||
where: and(eq(recovery_codes.used, false), eq(recovery_codes.userId, userId)),
|
||||
});
|
||||
for (const code of userRecoveryCodes) {
|
||||
const validRecoveryCode = await new Argon2id().verify(code.code, recoveryCode);
|
||||
if (validRecoveryCode) {
|
||||
await db
|
||||
.update(recovery_codes)
|
||||
.set({
|
||||
used: true,
|
||||
})
|
||||
.where(eq(recovery_codes.id, code.id));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
{#if form?.twoFactorRequired}
|
||||
<Form.Field form={superLoginForm} name="totpToken">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="totpToken">2FA Code</Form.Label>
|
||||
<Form.Label for="totpToken">Two Factor Code or Recovery Code</Form.Label>
|
||||
<Input {...attrs} autocomplete="one-time-code" bind:value={$loginForm.totpToken} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export const recovery_codes = pgTable('recovery_codes', {
|
|||
updated_at: timestamp('updated_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;
|
||||
|
||||
export const user_relations = relations(users, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}));
|
||||
|
|
|
|||
Loading…
Reference in a new issue