feat(frontend/otp): allow changing otp status and showing qrcode
This commit is contained in:
parent
bb7037d515
commit
00e4f522ab
4 changed files with 359 additions and 59 deletions
|
|
@ -16,7 +16,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"qrcode": "^1.5.4",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwindcss": "^4.1.17"
|
||||
}
|
||||
|
|
|
|||
255
frontend/pnpm-lock.yaml
generated
255
frontend/pnpm-lock.yaml
generated
|
|
@ -10,10 +10,16 @@ importers:
|
|||
dependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
version: 4.1.17(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
'@types/qrcode':
|
||||
specifier: ^1.5.6
|
||||
version: 1.5.6
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
qrcode:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
socket.io-client:
|
||||
specifier: ^4.8.1
|
||||
version: 4.8.1
|
||||
|
|
@ -29,10 +35,10 @@ importers:
|
|||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^7.2.7
|
||||
version: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
version: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -417,6 +423,34 @@ packages:
|
|||
'@types/js-cookie@3.0.6':
|
||||
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||
|
||||
'@types/node@24.10.2':
|
||||
resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==}
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
camelcase@5.3.1:
|
||||
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
debug@4.3.7:
|
||||
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
|
@ -435,10 +469,20 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dijkstrajs@1.0.3:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
engine.io-client@6.6.3:
|
||||
resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
|
||||
|
||||
|
|
@ -464,17 +508,29 @@ packages:
|
|||
picomatch:
|
||||
optional: true
|
||||
|
||||
find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
|
@ -553,6 +609,10 @@ packages:
|
|||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
|
|
@ -564,6 +624,22 @@ packages:
|
|||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
p-limit@2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
|
|
@ -571,15 +647,34 @@ packages:
|
|||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
qrcode@1.5.4:
|
||||
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
rollup@4.53.3:
|
||||
resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
socket.io-client@4.8.1:
|
||||
resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
|
@ -592,6 +687,14 @@ packages:
|
|||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
tailwindcss@4.1.17:
|
||||
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
|
||||
|
||||
|
|
@ -618,6 +721,9 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
vite-tsconfig-paths@5.1.4:
|
||||
resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
|
||||
peerDependencies:
|
||||
|
|
@ -666,6 +772,13 @@ packages:
|
|||
yaml:
|
||||
optional: true
|
||||
|
||||
which-module@2.0.1:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ws@8.17.1:
|
||||
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
|
@ -682,6 +795,17 @@ packages:
|
|||
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
y18n@4.0.3:
|
||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yargs@15.4.1:
|
||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
|
|
@ -910,17 +1034,45 @@ snapshots:
|
|||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.17
|
||||
|
||||
'@tailwindcss/vite@4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2))':
|
||||
'@tailwindcss/vite@4.1.17(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.17
|
||||
'@tailwindcss/oxide': 4.1.17
|
||||
tailwindcss: 4.1.17
|
||||
vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/js-cookie@3.0.6': {}
|
||||
|
||||
'@types/node@24.10.2':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
dependencies:
|
||||
'@types/node': 24.10.2
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
camelcase@5.3.1: {}
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
debug@4.3.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
|
@ -929,8 +1081,14 @@ snapshots:
|
|||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
dijkstrajs@1.0.3: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
engine.io-client@6.6.3:
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.2
|
||||
|
|
@ -983,13 +1141,22 @@ snapshots:
|
|||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
find-up@4.1.0:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
globrex@0.1.2: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
js-cookie@3.0.5: {}
|
||||
|
|
@ -1043,6 +1210,10 @@ snapshots:
|
|||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
locate-path@5.0.0:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
|
@ -1051,16 +1222,40 @@ snapshots:
|
|||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
rollup@4.53.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
|
@ -1089,6 +1284,8 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.53.3
|
||||
fsevents: 2.3.3
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
|
||||
socket.io-client@4.8.1:
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.2
|
||||
|
|
@ -1109,6 +1306,16 @@ snapshots:
|
|||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
tailwindcss@4.1.17: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
|
@ -1124,18 +1331,20 @@ snapshots:
|
|||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)):
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
|
|
@ -1144,10 +1353,40 @@ snapshots:
|
|||
rollup: 4.53.3
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
ws@8.17.1: {}
|
||||
|
||||
xmlhttprequest-ssl@2.1.2: {}
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
|
||||
yargs@15.4.1:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p id="totpSecretBox" class="mt-3 text-sm bg-gray-100 border p-2 rounded hidden"></p>
|
||||
<div id="totpSecretBox" class="mt-3 text-sm bg-gray-100 border p-2 rounded hidden">
|
||||
<canvas id="totpSecretCanvas" class="w-full h-full block"> </canvas>
|
||||
<div id="totpSecretText"
|
||||
class="w-full max-w-md p-3 border border-gray-300 rounded-lg overflow-y-auto bg-white text-gray-800 whitespace-pre-wrap">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,109 +1,164 @@
|
|||
import { addRoute, navigateTo, setTitle } from "@app/routing";
|
||||
import { showError } from "@app/toast";
|
||||
import page from './profile.html?raw'
|
||||
import page from "./profile.html?raw";
|
||||
import { updateUser } from "@app/auth";
|
||||
import { isNullish } from "@app/utils";
|
||||
import client from "@app/api";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
type OAuthQRCodeOptions = {
|
||||
label?: string; // e.g. your-app:user@example.com
|
||||
issuer?: string; // e.g. "YourApp"
|
||||
algorithm?: "SHA1" | "SHA256" | "SHA512";
|
||||
digits?: number;
|
||||
period?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders an OAuth2-compatible TOTP QR code into a canvas.
|
||||
*
|
||||
* @param canvas HTMLCanvasElement to draw into
|
||||
* @param secret Base32-encoded shared secret
|
||||
* @param options Meta data for QR (label, issuer, etc.)
|
||||
*/
|
||||
export async function renderOAuth2QRCode(
|
||||
canvas: HTMLCanvasElement,
|
||||
secret: string,
|
||||
): Promise<void> {
|
||||
// Encode the otpauth:// URL
|
||||
const otpauthUrl = new URL(`otpauth://totp/ft_boule:totp`);
|
||||
|
||||
otpauthUrl.searchParams.set("secret", secret.replace(/=+$/, ""));
|
||||
otpauthUrl.searchParams.set("issuer", "ft_boule");
|
||||
|
||||
// Render QR code into the canvas
|
||||
await QRCode.toCanvas(canvas, otpauthUrl.toString(), {
|
||||
margin: 1,
|
||||
scale: 5,
|
||||
});
|
||||
canvas.style.width = "";
|
||||
canvas.style.height = "";
|
||||
}
|
||||
|
||||
async function route(url: string, _args: { [k: string]: string }) {
|
||||
setTitle('Edit Profile')
|
||||
setTitle("Edit Profile");
|
||||
return {
|
||||
html: page, postInsert: async (app: HTMLElement | undefined) => {
|
||||
html: page,
|
||||
postInsert: async (app: HTMLElement | undefined) => {
|
||||
const user = await updateUser();
|
||||
if (isNullish(user))
|
||||
return showError('No User');
|
||||
if (isNullish(app))
|
||||
return showError('Failed to render');
|
||||
if (isNullish(user)) return showError("No User");
|
||||
if (isNullish(app)) return showError("Failed to render");
|
||||
let totpState = await (async () => {
|
||||
let res = await client.statusOtp();
|
||||
if (res.kind === "success")
|
||||
return {
|
||||
enabled: (res.msg as string) === "statusOtp.success.enabled",
|
||||
secret: ((res.msg as string) === "statusOtp.success.enabled") ? res.payload.secret : null,
|
||||
enabled:
|
||||
(res.msg as string) === "statusOtp.success.enabled",
|
||||
secret:
|
||||
(res.msg as string) === "statusOtp.success.enabled"
|
||||
? res.payload.secret
|
||||
: null,
|
||||
};
|
||||
else {
|
||||
showError('Failed to get OTP status')
|
||||
showError("Failed to get OTP status");
|
||||
return {
|
||||
enabled: false, secret: null,
|
||||
}
|
||||
enabled: false,
|
||||
secret: null,
|
||||
};
|
||||
}
|
||||
|
||||
})()
|
||||
})();
|
||||
// ---- Simulated State ----
|
||||
let totpEnabled = totpState.enabled;
|
||||
let totpSecret = totpState.secret; // would come from backend
|
||||
|
||||
let guestBox = app.querySelector<HTMLDivElement>("#isGuestBox")!;
|
||||
let displayNameWrapper = app.querySelector<HTMLDivElement>("#displayNameWrapper")!;
|
||||
let displayNameBox = app.querySelector<HTMLInputElement>("#displayNameBox")!;
|
||||
let displayNameButton = app.querySelector<HTMLButtonElement>("#displayNameButton")!;
|
||||
let loginNameWrapper = app.querySelector<HTMLDivElement>("#loginNameWrapper")!;
|
||||
let loginNameBox = app.querySelector<HTMLDivElement>("#loginNameBox")!;
|
||||
let passwordWrapper = app.querySelector<HTMLDivElement>("#passwordWrapper")!;
|
||||
let passwordBox = app.querySelector<HTMLInputElement>("#passwordBox")!;
|
||||
let passwordButton = app.querySelector<HTMLButtonElement>("#passwordButton")!;
|
||||
|
||||
let displayNameWrapper = app.querySelector<HTMLDivElement>(
|
||||
"#displayNameWrapper",
|
||||
)!;
|
||||
let displayNameBox =
|
||||
app.querySelector<HTMLInputElement>("#displayNameBox")!;
|
||||
let displayNameButton =
|
||||
app.querySelector<HTMLButtonElement>("#displayNameButton")!;
|
||||
let loginNameWrapper =
|
||||
app.querySelector<HTMLDivElement>("#loginNameWrapper")!;
|
||||
let loginNameBox =
|
||||
app.querySelector<HTMLDivElement>("#loginNameBox")!;
|
||||
let passwordWrapper =
|
||||
app.querySelector<HTMLDivElement>("#passwordWrapper")!;
|
||||
let passwordBox =
|
||||
app.querySelector<HTMLInputElement>("#passwordBox")!;
|
||||
let passwordButton =
|
||||
app.querySelector<HTMLButtonElement>("#passwordButton")!;
|
||||
|
||||
if (!isNullish(user.selfInfo?.loginName))
|
||||
loginNameBox.innerText = user.selfInfo?.loginName;
|
||||
else
|
||||
loginNameBox.innerHTML = '<span class="text-red-600 font-bold mb-1">You don\'t have a login name</span>';
|
||||
loginNameBox.innerHTML =
|
||||
'<span class="text-red-600 font-bold mb-1">You don\'t have a login name</span>';
|
||||
displayNameBox.value = user.name;
|
||||
|
||||
guestBox.hidden = !user.guest;
|
||||
|
||||
// ---- DOM Elements ----
|
||||
const totpStatusText = app.querySelector("#totpStatusText")!;
|
||||
const enableBtn = app.querySelector<HTMLButtonElement>("#enableTotp")!;
|
||||
const disableBtn = app.querySelector<HTMLButtonElement>("#disableTotp")!;
|
||||
const showSecretBtn = app.querySelector<HTMLButtonElement>("#showSecret")!;
|
||||
const enableBtn =
|
||||
app.querySelector<HTMLButtonElement>("#enableTotp")!;
|
||||
const disableBtn =
|
||||
app.querySelector<HTMLButtonElement>("#disableTotp")!;
|
||||
const showSecretBtn =
|
||||
app.querySelector<HTMLButtonElement>("#showSecret")!;
|
||||
const secretBox = app.querySelector("#totpSecretBox")!;
|
||||
const secretText =
|
||||
app.querySelector<HTMLDivElement>("#totpSecretText")!;
|
||||
const secretCanvas =
|
||||
app.querySelector<HTMLCanvasElement>("#totpSecretCanvas")!;
|
||||
|
||||
if (user.guest) {
|
||||
for (let c of passwordButton.classList.values()) {
|
||||
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
|
||||
if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
|
||||
passwordButton.classList.remove(c);
|
||||
}
|
||||
passwordButton.disabled = true;
|
||||
passwordButton.classList.add('bg-gray-700', 'hover:bg-gray-700');
|
||||
passwordButton.classList.add(
|
||||
"bg-gray-700",
|
||||
"hover:bg-gray-700",
|
||||
);
|
||||
|
||||
passwordBox.disabled = true;
|
||||
passwordBox.classList.add('color-white');
|
||||
passwordBox.classList.add("color-white");
|
||||
|
||||
for (let c of displayNameButton.classList.values()) {
|
||||
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
|
||||
if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
|
||||
displayNameButton.classList.remove(c);
|
||||
}
|
||||
displayNameButton.disabled = true;
|
||||
displayNameButton.classList.add('bg-gray-700');
|
||||
displayNameButton.classList.add('color-white');
|
||||
displayNameButton.classList.add("bg-gray-700");
|
||||
displayNameButton.classList.add("color-white");
|
||||
|
||||
displayNameBox.disabled = true;
|
||||
displayNameBox.classList.add('color-white');
|
||||
displayNameBox.classList.add("color-white");
|
||||
|
||||
for (let c of enableBtn.classList.values()) {
|
||||
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
|
||||
if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
|
||||
enableBtn.classList.remove(c);
|
||||
}
|
||||
for (let c of disableBtn.classList.values()) {
|
||||
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
|
||||
if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
|
||||
disableBtn.classList.remove(c);
|
||||
}
|
||||
for (let c of showSecretBtn.classList.values()) {
|
||||
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
|
||||
if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
|
||||
showSecretBtn.classList.remove(c);
|
||||
}
|
||||
enableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
|
||||
disableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
|
||||
showSecretBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
|
||||
enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
|
||||
disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
|
||||
showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
|
||||
|
||||
enableBtn.disabled = true;
|
||||
disableBtn.disabled = true;
|
||||
showSecretBtn.disabled = true;
|
||||
}
|
||||
|
||||
|
||||
// ---- Update UI ----
|
||||
function refreshTotpUI() {
|
||||
if (totpEnabled) {
|
||||
|
|
@ -127,8 +182,7 @@ async function route(url: string, _args: { [k: string]: string }) {
|
|||
let res = await client.enableOtp();
|
||||
if (res.kind === "success") {
|
||||
navigateTo(url);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
showError(`failed to activate OTP: ${res.msg}`);
|
||||
}
|
||||
};
|
||||
|
|
@ -137,23 +191,23 @@ async function route(url: string, _args: { [k: string]: string }) {
|
|||
let res = await client.disableOtp();
|
||||
if (res.kind === "success") {
|
||||
navigateTo(url);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
showError(`failed to deactivate OTP: ${res.msg}`);
|
||||
}
|
||||
};
|
||||
|
||||
showSecretBtn.onclick = () => {
|
||||
secretBox.textContent = `TOTP Secret: ${totpSecret}`;
|
||||
if (!isNullish(totpSecret)) {
|
||||
secretText.textContent = totpSecret;
|
||||
renderOAuth2QRCode(secretCanvas, totpSecret);
|
||||
}
|
||||
secretBox.classList.toggle("hidden");
|
||||
};
|
||||
|
||||
// Initialize UI state
|
||||
refreshTotpUI();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
addRoute('/profile', route)
|
||||
addRoute("/profile", route);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue