Adding two factor code generation.

This commit is contained in:
Bradley Shellnut 2024-03-29 17:40:02 -07:00
parent f729becade
commit 8c5cda1ebc
14 changed files with 1955 additions and 65 deletions

View file

@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "two_factor_secret" text;

File diff suppressed because it is too large Load diff

View file

@ -57,6 +57,13 @@
"when": 1710905572670,
"tag": "0007_large_miss_america",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1711757183163,
"tag": "0008_amusing_franklin_richards",
"breakpoints": true
}
]
}

View file

@ -24,7 +24,7 @@
"@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.76.2",
"@playwright/test": "^1.42.1",
"@resvg/resvg-js": "^2.6.0",
"@resvg/resvg-js": "^2.6.1",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/enhanced-img": "^0.1.9",
"@sveltejs/kit": "^2.5.4",
@ -106,6 +106,7 @@
"oslo": "^1.1.3",
"pg": "^8.11.3",
"postgres": "^3.4.4",
"qrcode": "^1.5.3",
"radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0",

View file

@ -98,6 +98,9 @@ dependencies:
postgres:
specifier: ^3.4.4
version: 3.4.4
qrcode:
specifier: ^1.5.3
version: 1.5.3
radix-svelte:
specifier: ^0.9.0
version: 0.9.0(svelte@4.2.12)
@ -131,8 +134,8 @@ devDependencies:
specifier: ^1.42.1
version: 1.42.1
'@resvg/resvg-js':
specifier: ^2.6.0
version: 2.6.0
specifier: ^2.6.1
version: 2.6.1
'@sveltejs/adapter-auto':
specifier: ^3.1.1
version: 3.1.1(@sveltejs/kit@2.5.4)
@ -2736,8 +2739,8 @@ packages:
requiresBuild: true
optional: true
/@resvg/resvg-js-android-arm-eabi@2.6.0:
resolution: {integrity: sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==}
/@resvg/resvg-js-android-arm-eabi@2.6.1:
resolution: {integrity: sha512-oXmXUUqTzinvXwkVBDdNKocAeF1zLGJYasTNRmoqF3gyOm04qRYT1On0m6oK2jbTiUAOUTqi0ZSizcecnwcSDA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
@ -2745,8 +2748,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-android-arm64@2.6.0:
resolution: {integrity: sha512-N527f529bjMwYWShZYfBD60dXA4Fux+D695QsHQ93BDYZSHUoOh1CUGUyICevnTxs7VgEl98XpArmUWBZQVMfQ==}
/@resvg/resvg-js-android-arm64@2.6.1:
resolution: {integrity: sha512-vcCZoBx8s/3/+t6nDd9fB/LL70I4B2YmgDT7uP6wyqVCUPniNeLR9VYIuvpMHw6oVyL5Mpt8F2YXV1zQE2X2hw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
@ -2754,8 +2757,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-darwin-arm64@2.6.0:
resolution: {integrity: sha512-MabUKLVayEwlPo0mIqAmMt+qESN8LltCvv5+GLgVga1avpUrkxj/fkU1TKm8kQegutUjbP/B0QuMuUr0uhF8ew==}
/@resvg/resvg-js-darwin-arm64@2.6.1:
resolution: {integrity: sha512-uO0WvEQqQlAL8u7nI7k1yL5wSsZYU2YCSsN1hAhr1LjyvmWmyC09xUEdWPUVVT1nL2YK4Ueh0LR+pxOT3QlCng==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
@ -2763,8 +2766,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-darwin-x64@2.6.0:
resolution: {integrity: sha512-zrFetdnSw/suXjmyxSjfDV7i61hahv6DDG6kM7BYN2yJ3Es5+BZtqYZTcIWogPJedYKmzN1YTMWGd/3f0ubFiA==}
/@resvg/resvg-js-darwin-x64@2.6.1:
resolution: {integrity: sha512-aW15HMQSk85GPHE4gsc56G0Fqi2IGVkDfPWEWHEyDBpCZ17RKweAwg5V3ioz9aGX1nmhjQa9tJ2xgVwX+sqIjw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
@ -2772,8 +2775,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-linux-arm-gnueabihf@2.6.0:
resolution: {integrity: sha512-sH4gxXt7v7dGwjGyzLwn7SFGvwZG6DQqLaZ11MmzbCwd9Zosy1TnmrMJfn6TJ7RHezmQMgBPi18bl55FZ1AT4A==}
/@resvg/resvg-js-linux-arm-gnueabihf@2.6.1:
resolution: {integrity: sha512-7vpBFzCMONnRzK0uCBT5h+Dmsa8dWsoLFqB6xgutNfKkldjuCOiLNe0tT7hneGF8tw5H+W6hX/VLx2ktDwsS4Q==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
@ -2781,8 +2784,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-linux-arm64-gnu@2.6.0:
resolution: {integrity: sha512-fCyMncqCJtrlANADIduYF4IfnWQ295UKib7DAxFXQhBsM9PLDTpizr0qemZcCNadcwSVHnAIzL4tliZhCM8P6A==}
/@resvg/resvg-js-linux-arm64-gnu@2.6.1:
resolution: {integrity: sha512-+Gi3OIOJFFiCdm72AsDa7KPnkogitLQ6yfF1O/J25adUrlWjvKAM9+8b5sTI9waeLERZHNJpIVESpdIxI2/5sQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@ -2790,8 +2793,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-linux-arm64-musl@2.6.0:
resolution: {integrity: sha512-ouLjTgBQHQyxLht4FdMPTvuY8xzJigM9EM2Tlu0llWkN1mKyTQrvYWi6TA6XnKdzDJHy7ZLpWpjZi7F5+Pg+Vg==}
/@resvg/resvg-js-linux-arm64-musl@2.6.1:
resolution: {integrity: sha512-lnRGWG/LwpX6UsV1neHAavPnek3WlCnGMdBZ/7JbpamK5VmtDZmsV2esOFpME6pKnWL40UX4WjPsCBtKkhMUMA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@ -2799,8 +2802,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-linux-x64-gnu@2.6.0:
resolution: {integrity: sha512-n3zC8DWsvxC1AwxpKFclIPapDFibs5XdIRoV/mcIlxlh0vseW1F49b97F33BtJQRmlntsqqN6GMMqx8byB7B+Q==}
/@resvg/resvg-js-linux-x64-gnu@2.6.1:
resolution: {integrity: sha512-2S1N7fHl5480AUrUtxsfjFOh3t8NQ2qKavROZRDKWJqFXBrNOUsirDa33LtpFGDkFW18SjP/FCs1xfHLzzz43g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@ -2808,8 +2811,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-linux-x64-musl@2.6.0:
resolution: {integrity: sha512-n4tasK1HOlAxdTEROgYA1aCfsEKk0UOFDNd/AQTTZlTmCbHKXPq+O8npaaKlwXquxlVK8vrkcWbksbiGqbCAcw==}
/@resvg/resvg-js-linux-x64-musl@2.6.1:
resolution: {integrity: sha512-G5GMmpvFiyclkp44eVPVWnN2lhLx9eMIcxGnBFWjnpI3TxrjBt7aVic2N8CsZ0vt2rrnJkQI0IKjUQQIi138Hw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@ -2817,8 +2820,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-win32-arm64-msvc@2.6.0:
resolution: {integrity: sha512-X2+EoBJFwDI5LDVb51Sk7ldnVLitMGr9WwU/i21i3fAeAXZb3hM16k67DeTy16OYkT2dk/RfU1tP1wG+rWbz2Q==}
/@resvg/resvg-js-win32-arm64-msvc@2.6.1:
resolution: {integrity: sha512-m9TBMrGs2tML0oz14D/x40tPedqCgNFy/DH7/z/bvnP9bH59fivaInmHTjd8oSFfGRZ/DasXMFcAL+LS1+hfSQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
@ -2826,8 +2829,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-win32-ia32-msvc@2.6.0:
resolution: {integrity: sha512-L7oevWjQoUgK5W1fCKn0euSVemhDXVhrjtwqpc7MwBKKimYeiOshO1Li1pa8bBt5PESahenhWgdB6lav9O0fEg==}
/@resvg/resvg-js-win32-ia32-msvc@2.6.1:
resolution: {integrity: sha512-Ma+MJXesViT0A7JqTobsB9DOCO0AkfmLxsgkvxq0IiWkpU9Z4Gp+RkDsFQbMhJwVXaz7b8L6y+EIvf95iCbJQw==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
@ -2835,8 +2838,8 @@ packages:
dev: true
optional: true
/@resvg/resvg-js-win32-x64-msvc@2.6.0:
resolution: {integrity: sha512-8lJlghb+Unki5AyKgsnFbRJwkEj9r1NpwyuBG8yEJiG1W9eEGl03R3I7bsVa3haof/3J1NlWf0rzSa1G++A2iw==}
/@resvg/resvg-js-win32-x64-msvc@2.6.1:
resolution: {integrity: sha512-mWIlgEuFWBrlldCbhLPvG4tt0r0D1RZ8eR2+zxTtQ15d/lbVjwnGpw2l1noV3zhV5S6mAVzoZeQ1emoov63Y/A==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -2844,22 +2847,22 @@ packages:
dev: true
optional: true
/@resvg/resvg-js@2.6.0:
resolution: {integrity: sha512-Tf3YpbBKcQn991KKcw/vg7vZf98v01seSv6CVxZBbRkL/xyjnoYB6KgrFL6zskT1A4dWC/vg77KyNOW+ePaNlA==}
/@resvg/resvg-js@2.6.1:
resolution: {integrity: sha512-CVGUWPvgr3b96+PooncXCsvu93CMwfEoueqIxIJr9AuUA8OaIPcZjOgFhas62fcHRdWMmxQqF1Rp+89bQsj/RA==}
engines: {node: '>= 10'}
optionalDependencies:
'@resvg/resvg-js-android-arm-eabi': 2.6.0
'@resvg/resvg-js-android-arm64': 2.6.0
'@resvg/resvg-js-darwin-arm64': 2.6.0
'@resvg/resvg-js-darwin-x64': 2.6.0
'@resvg/resvg-js-linux-arm-gnueabihf': 2.6.0
'@resvg/resvg-js-linux-arm64-gnu': 2.6.0
'@resvg/resvg-js-linux-arm64-musl': 2.6.0
'@resvg/resvg-js-linux-x64-gnu': 2.6.0
'@resvg/resvg-js-linux-x64-musl': 2.6.0
'@resvg/resvg-js-win32-arm64-msvc': 2.6.0
'@resvg/resvg-js-win32-ia32-msvc': 2.6.0
'@resvg/resvg-js-win32-x64-msvc': 2.6.0
'@resvg/resvg-js-android-arm-eabi': 2.6.1
'@resvg/resvg-js-android-arm64': 2.6.1
'@resvg/resvg-js-darwin-arm64': 2.6.1
'@resvg/resvg-js-darwin-x64': 2.6.1
'@resvg/resvg-js-linux-arm-gnueabihf': 2.6.1
'@resvg/resvg-js-linux-arm64-gnu': 2.6.1
'@resvg/resvg-js-linux-arm64-musl': 2.6.1
'@resvg/resvg-js-linux-x64-gnu': 2.6.1
'@resvg/resvg-js-linux-x64-musl': 2.6.1
'@resvg/resvg-js-win32-arm64-msvc': 2.6.1
'@resvg/resvg-js-win32-ia32-msvc': 2.6.1
'@resvg/resvg-js-win32-x64-msvc': 2.6.1
dev: true
/@resvg/resvg-wasm@2.6.0:
@ -3216,8 +3219,8 @@ packages:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
/@sinclair/typebox@0.32.19:
resolution: {integrity: sha512-AUphm4Py6t+q5b6TU8OA9YRgeGfogsWy2oZvYLRCxt6d+mPkT0Mc4j+BusQYMGJH47DJe3/fKkLtmWPrKP1nzQ==}
/@sinclair/typebox@0.32.20:
resolution: {integrity: sha512-ziK497ILSIYMxD/thl496idIb03IZPlha04itLQu1xAFQbumWZ+Dj4PMMCkDRpAYhvVSdmRlTjGu2B2MA5RplQ==}
requiresBuild: true
optional: true
@ -3683,7 +3686,6 @@ packages:
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
@ -3888,6 +3890,11 @@ packages:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: false
/camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'}
@ -3992,6 +3999,14 @@ packages:
timers-ext: 0.1.7
dev: true
/cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/clsx@2.0.0:
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
engines: {node: '>=6'}
@ -4016,7 +4031,6 @@ packages:
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
@ -4172,6 +4186,11 @@ packages:
dependencies:
ms: 2.1.2
/decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: false
/deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'}
@ -4243,6 +4262,10 @@ packages:
heap: 0.2.7
dev: true
/dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dev: false
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -4389,6 +4412,10 @@ packages:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/encode-utf8@1.0.3:
resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
dev: false
/env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -4834,6 +4861,14 @@ packages:
dependencies:
to-regex-range: 5.0.1
/find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: false
/find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@ -4929,6 +4964,11 @@ packages:
wide-align: 1.1.5
dev: false
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: false
/get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
dev: true
@ -5417,6 +5457,13 @@ packages:
/locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: false
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@ -5836,6 +5883,13 @@ packages:
'@node-rs/bcrypt': 1.9.0
dev: false
/p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: false
/p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@ -5849,12 +5903,24 @@ packages:
yocto-queue: 1.0.0
dev: true
/p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: false
/p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
dependencies:
p-limit: 3.1.0
/p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: false
/packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
dev: false
@ -6041,6 +6107,11 @@ packages:
fsevents: 2.3.2
dev: true
/pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
dev: false
/postcss-attribute-case-insensitive@6.0.3(postcss@8.4.38):
resolution: {integrity: sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==}
engines: {node: ^14 || ^16 || >=18}
@ -6611,6 +6682,17 @@ packages:
engines: {node: '>=6'}
dev: true
/qrcode@1.5.3:
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
engines: {node: '>=10.13.0'}
hasBin: true
dependencies:
dijkstrajs: 1.0.3
encode-utf8: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
dev: false
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -6668,6 +6750,15 @@ packages:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
dev: false
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: false
/require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: false
/resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -7278,6 +7369,23 @@ packages:
peerDependencies:
'@sveltejs/kit': 1.x || 2.x
svelte: 3.x || 4.x || >=5.0.0-next.51
peerDependenciesMeta:
'@sinclair/typebox':
optional: true
'@vinejs/vine':
optional: true
arktype:
optional: true
joi:
optional: true
superstruct:
optional: true
valibot:
optional: true
yup:
optional: true
zod:
optional: true
dependencies:
'@sveltejs/kit': 2.5.4(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.2.6)
devalue: 4.3.2
@ -7287,7 +7395,7 @@ packages:
ts-deepmerge: 7.0.0
optionalDependencies:
'@gcornut/valibot-json-schema': 0.0.26(@types/json-schema@7.0.15)(esbuild-runner@2.2.2)(esbuild@0.20.2)(valibot@0.30.0)
'@sinclair/typebox': 0.32.19
'@sinclair/typebox': 0.32.20
'@sodaru/yup-to-json-schema': 2.0.1
'@vinejs/vine': 1.8.0
arktype: 1.0.29-alpha
@ -7781,6 +7889,10 @@ packages:
webidl-conversions: 3.0.1
dev: false
/which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
dev: false
/which-typed-array@1.1.13:
resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==}
engines: {node: '>= 0.4'}
@ -7818,6 +7930,15 @@ packages:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
dev: true
/wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@ -7826,6 +7947,10 @@ packages:
engines: {node: '>=0.4'}
dev: false
/y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: false
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
@ -7838,6 +7963,31 @@ packages:
resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
engines: {node: '>= 14'}
/yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}

View file

@ -23,6 +23,13 @@ export const changeUserPasswordSchema = z
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()
});
export type AddTwoFactorSchema = typeof addTwoFactorSchema;
export const updateUserPasswordSchema = userSchema
.pick({ password: true, confirm_password: true })
.superRefine(({ confirm_password, password }, ctx) => {

View file

@ -94,19 +94,19 @@
</form>
<div class="mt-6">
{#if !hasSetupTwoFactor}
<Button variant="link" class="text-secondary-foreground" href="/two-factor/setup">
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
<KeyRound class="mr-2 h-4 w-4" />
Setup 2FA
</Button>
{:else}
<Button variant="link" class="text-secondary-foreground" href="/two-factor/disable">
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
<KeyRound class="mr-2 h-4 w-4" />
Disable 2FA
</Button>
{/if}
</div>
<div class="mt-6">
<Button variant="link" class="text-secondary-foreground" href="/password/change">
<Button variant="link" class="text-secondary-foreground" href="/profile/security/password/change">
<KeyRound class="mr-2 h-4 w-4" />
Change Password
</Button>

View file

@ -1,16 +1,16 @@
import { fail, type Actions } from "@sveltejs/kit";
import { eq } from "drizzle-orm";
import { fail, type Actions } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'
import { Argon2id } from "oslo/password";
import db from "$lib/drizzle";
import { redirect } from 'sveltekit-flash-message/server';
import { Argon2id } from 'oslo/password';
import type { PageServerLoad } from '../../../$types';
import db from '$lib/drizzle';
import { changeUserPasswordSchema } from '$lib/validations/account';
import { lucia } from '$lib/server/auth.js';
import type { PageServerLoad } from "./$types";
import { users } from "../../../../../schema";
import { notSignedInMessage } from "$lib/flashMessages";
import type { Cookie } from "lucia";
import { users } from '../../../../../../../schema';
import { notSignedInMessage } from '$lib/flashMessages';
import type { Cookie } from 'lucia';
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, zod(changeUserPasswordSchema));
@ -42,7 +42,7 @@ export const actions: Actions = {
console.log('updating profile');
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
redirect(302, '/login', notSignedInMessage, event);
}
if (!event.locals.session) {
@ -65,7 +65,10 @@ export const actions: Actions = {
);
}
const currentPasswordVerified = await new Argon2id().verify(dbUser.hashed_password, form.data.current_password);
const currentPasswordVerified = await new Argon2id().verify(
dbUser.hashed_password,
form.data.current_password
);
if (!currentPasswordVerified) {
return setError(form, 'current_password', 'Your password is incorrect');
@ -78,11 +81,12 @@ export const actions: Actions = {
}
const hashedPassword = await new Argon2id().hash(form.data.password);
await lucia.invalidateUserSessions(user.id);
await db.update(users)
await db
.update(users)
.set({ hashed_password: hashedPassword })
.where(eq(users.id, user.id));
await lucia.createSession(user.id, {
country: event.locals.session?.ipCountry ?? 'unknown',
country: event.locals.session?.ipCountry ?? 'unknown'
});
sessionCookie = lucia.createBlankSessionCookie();
} catch (e) {
@ -93,7 +97,7 @@ export const actions: Actions = {
return setError(form, 'current_password', 'Your password is incorrect.');
}
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: ".",
path: '.',
...sessionCookie.attributes
});

View file

@ -2,8 +2,8 @@
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { AlertTriangle } from 'lucide-svelte';
import * as Alert from "$lib/components/ui/alert";
import * as Form from '$lib/components/ui/form';
import * as Alert from "$components/ui/alert";
import * as Form from '$components/ui/form';
import { Input } from '$components/ui/input';
import { changeUserPasswordSchema } from '$lib/validations/account';

View file

@ -0,0 +1,121 @@
import { encodeHex } from 'oslo/encoding';
import { Argon2id } from 'oslo/password';
import { createTOTPKeyURI } from 'oslo/otp';
import { HMAC } from 'oslo/crypto';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import type { Cookie } from 'lucia';
import type { PageServerLoad } from '../../$types';
import { addTwoFactorSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages';
import { type Actions, fail } from '@sveltejs/kit';
import db from '$lib/drizzle';
import { eq } from 'drizzle-orm';
import { users } from '../../../../../../schema';
import QRCode from 'qrcode';
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, zod(addTwoFactorSchema));
const user = event.locals.user;
if (!user) {
redirect(302, '/login', notSignedInMessage, event);
}
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
await db
.update(users)
.set({ two_factor_secret: encodeHex(twoFactorSecret) })
.where(eq(users.id, user.id));
const issuer = 'bored-game';
const accountName = user.email || user.username;
// pass the website's name and the user identifier (e.g. email, username)
const uri = createTOTPKeyURI(issuer, accountName, twoFactorSecret);
const qrCode = await QRCode.toDataURL(uri);
form.data = {
current_password: '',
two_factor_code: ''
};
return {
form,
qrCode
};
};
export const actions: Actions = {
default: async (event) => {
const form = await superValidate(event, zod(addTwoFactorSchema));
if (!form.valid) {
return fail(400, {
form
});
}
console.log('updating profile');
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) {
form.data.current_password = '';
form.data.two_factor_code = '';
return setError(
form,
'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
);
if (!currentPasswordVerified) {
return setError(form, 'current_password', 'Your password is incorrect');
}
if (user?.username) {
let sessionCookie: Cookie;
try {
} catch (e) {
console.error(e);
form.data.password = '';
form.data.confirm_password = '';
form.data.current_password = '';
return setError(form, 'current_password', 'Your password is incorrect.');
}
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes
});
const message = {
type: 'success',
message: 'Password Updated. Please sign in.'
} as const;
redirect(302, '/login', message, event);
}
return setError(
form,
'Error occurred. Please try again or contact support if you need further help.'
);
// TODO: Add toast instead?
// form.data.password = '';
// form.data.confirm_password = '';
// form.data.current_password = '';
// return message(form, 'Profile updated successfully.');
}
};

View file

@ -0,0 +1,29 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { AlertTriangle } from 'lucide-svelte';
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';
export let data;
const { qrCode } = data;
console.log('qrCode', qrCode)
const form = superForm(data.form, {
taintedMessage: null,
validators: zodClient(addTwoFactorSchema),
delayMs: 500,
multipleSubmits: 'prevent'
});
const { form: formData, enhance } = form;
</script>
<h1>Two-Factor Authentication</h1>
<img src={qrCode} alt="QR Code" />

View file

@ -0,0 +1,44 @@
// vite.config.ts
import { sveltekit } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/@sveltejs+kit@2.5.4_@sveltejs+vite-plugin-svelte@3.0.2_svelte@4.2.12_vite@5.2.6/node_modules/@sveltejs/kit/src/exports/vite/index.js";
import { defineConfig } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/vite@5.2.6_@types+node@20.11.30_sass@1.72.0/node_modules/vite/dist/node/index.js";
var vite_config_default = defineConfig({
plugins: [
// sentrySvelteKit({
// sourceMapsUploadOptions: {
// org: process.env.SENTRY_ORG,
// project: process.env.SENTRY_PROJECT,
// authToken: process.env.SENTRY_AUTH_TOKEN,
// cleanArtifacts: true,
// }
// }),
sveltekit()
],
test: {
include: ["src/**/*.{test,spec}.{js,ts}"]
},
define: {
SUPERFORMS_LEGACY: true
},
css: {
devSourcemap: true,
preprocessorOptions: {
postcss: {
additionalData: `
@custom-media --below_small (width < 400px);
@custom-media --below_med (width < 700px);
@custom-media --below_large (width < 900px);
@custom-media --below_xlarge (width < 1200px);
@custom-media --above_small (width > 400px);
@custom-media --above_med (width > 700px);
@custom-media --above_large (width > 900px);
@custom-media --above_xlarge (width > 1200px);
`
}
}
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL2JzaGVsbG51L3Byb2plY3RzL3dlYnNpdGVzL2JvcmVkZ2FtZS92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWUvdml0ZS5jb25maWcudHNcIjsvLyBpbXBvcnQgeyBzZW50cnlTdmVsdGVLaXQgfSBmcm9tIFwiQHNlbnRyeS9zdmVsdGVraXRcIjtcbmltcG9ydCB7IHN2ZWx0ZWtpdCB9IGZyb20gJ0BzdmVsdGVqcy9raXQvdml0ZSc7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcblxuLy8gVE9ETzogRml4IFNlbnRyeVxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcblx0cGx1Z2luczogW1xuXHRcdC8vIHNlbnRyeVN2ZWx0ZUtpdCh7XG5cdFx0Ly8gXHRzb3VyY2VNYXBzVXBsb2FkT3B0aW9uczoge1xuXHRcdC8vIFx0XHRvcmc6IHByb2Nlc3MuZW52LlNFTlRSWV9PUkcsXG5cdFx0Ly8gXHRcdHByb2plY3Q6IHByb2Nlc3MuZW52LlNFTlRSWV9QUk9KRUNULFxuXHRcdC8vIFx0XHRhdXRoVG9rZW46IHByb2Nlc3MuZW52LlNFTlRSWV9BVVRIX1RPS0VOLFxuXHRcdC8vIFx0XHRjbGVhbkFydGlmYWN0czogdHJ1ZSxcblx0XHQvLyBcdH1cblx0XHQvLyB9KSxcblx0XHRzdmVsdGVraXQoKVxuXHRdLFxuXHR0ZXN0OiB7XG5cdFx0aW5jbHVkZTogWydzcmMvKiovKi57dGVzdCxzcGVjfS57anMsdHN9J11cblx0fSxcblx0ZGVmaW5lOiB7XG5cdFx0U1VQRVJGT1JNU19MRUdBQ1k6IHRydWVcblx0fSxcblx0Y3NzOiB7XG5cdFx0ZGV2U291cmNlbWFwOiB0cnVlLFxuXHRcdHByZXByb2Nlc3Nvck9wdGlvbnM6IHtcblx0XHRcdHBvc3Rjc3M6IHtcblx0XHRcdFx0YWRkaXRpb25hbERhdGE6IGBcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X3NtYWxsICh3aWR0aCA8IDQwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X21lZCAod2lkdGggPCA3MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd19sYXJnZSAod2lkdGggPCA5MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd194bGFyZ2UgKHdpZHRoIDwgMTIwMHB4KTtcblxuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfc21hbGwgKHdpZHRoID4gNDAwcHgpO1xuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfbWVkICh3aWR0aCA+IDcwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX2xhcmdlICh3aWR0aCA+IDkwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX3hsYXJnZSAod2lkdGggPiAxMjAwcHgpO1xuXHRcdFx0XHRgXG5cdFx0XHR9XG5cdFx0fVxuXHR9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxpQkFBaUI7QUFDMUIsU0FBUyxvQkFBb0I7QUFHN0IsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDM0IsU0FBUztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQVNSLFVBQVU7QUFBQSxFQUNYO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDTCxTQUFTLENBQUMsOEJBQThCO0FBQUEsRUFDekM7QUFBQSxFQUNBLFFBQVE7QUFBQSxJQUNQLG1CQUFtQjtBQUFBLEVBQ3BCO0FBQUEsRUFDQSxLQUFLO0FBQUEsSUFDSixjQUFjO0FBQUEsSUFDZCxxQkFBcUI7QUFBQSxNQUNwQixTQUFTO0FBQUEsUUFDUixnQkFBZ0I7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BV2pCO0FBQUEsSUFDRDtBQUFBLEVBQ0Q7QUFDRCxDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=