feat: add CNB login functionality and user management

- Introduced `cnb-login` route to handle user login via CNB token.
- Created `CnbServices` class for managing CNB user interactions.
- Added `findByCnbId` method in the User model to retrieve users by CNB ID.
- Updated error handling to provide more structured error messages.
- Enhanced user creation logic to handle CNB users.
- Added tests for the new CNB login functionality.
This commit is contained in:
2026-02-20 23:30:53 +08:00
parent 1782a9ef19
commit 366a21d621
16 changed files with 392 additions and 40 deletions

226
pnpm-lock.yaml generated
View File

@@ -67,6 +67,9 @@ importers:
'@kevisual/api':
specifier: ^0.0.52
version: 0.0.52(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@kevisual/cnb':
specifier: ^0.0.28
version: 0.0.28(dotenv@17.3.1)(ioredis@5.9.3)
'@kevisual/context':
specifier: ^0.0.8
version: 0.0.8
@@ -83,8 +86,8 @@ importers:
specifier: ^0.0.4
version: 0.0.4
'@kevisual/router':
specifier: 0.0.80
version: 0.0.80
specifier: 0.0.83
version: 0.0.83
'@kevisual/types':
specifier: ^0.0.12
version: 0.0.12
@@ -667,6 +670,9 @@ packages:
'@kevisual/auth@2.0.3':
resolution: {integrity: sha512-4xpijaIhlCTr/DlJaV/gmkCQeg45EO1yxWpRvUX+1jCdVbuxSR0wZrF0SD9oybnjmKWMKDNPLsXyduFjMGcItA==}
'@kevisual/cnb@0.0.28':
resolution: {integrity: sha512-mv45B68D/lliPBUXEnxbofV+Ds/KSYXuGzzG7S8yEekwp31PwRjecP2dyE0Mxe+DlhFmsSYN09liVUHcCXDbOg==}
'@kevisual/context@0.0.4':
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
@@ -712,6 +718,9 @@ packages:
'@kevisual/router@0.0.80':
resolution: {integrity: sha512-rVwi6Yf411bnNm2x94lMm+s4Csw0Yb7u/aj+VJJ59iouAYhjLuL7Rs1EcARhnQf47cegBJi6zozfGHgLsLHN2w==}
'@kevisual/router@0.0.83':
resolution: {integrity: sha512-CVazzM1rXVyvU7QcMQr0/EuqacRNEGalThDDLGQcvKEVHyduJ9yWddn6kezgWFCpNlPKhzSCKkIFuZVixNVxDQ==}
'@kevisual/types@0.0.12':
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
@@ -724,6 +733,10 @@ packages:
resolution: {integrity: sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg==}
engines: {node: '>=10.0.0'}
'@kevisual/ws@8.19.0':
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
engines: {node: '>=10.0.0'}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
cpu: [arm64]
@@ -1192,6 +1205,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
@@ -1217,6 +1234,9 @@ packages:
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
engines: {node: '>= 14'}
cookie-es@1.2.2:
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@@ -1240,6 +1260,9 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
crossws@0.3.5:
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
@@ -1293,6 +1316,9 @@ packages:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
degenerator@5.0.1:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
@@ -1305,6 +1331,9 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -1603,6 +1632,9 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
h3@1.15.5:
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -1648,6 +1680,9 @@ packages:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -1748,6 +1783,10 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@11.2.6:
resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
engines: {node: 20 || >=22}
lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -1835,14 +1874,23 @@ packages:
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
node-fetch-native@1.6.7:
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
node-gyp-build-optional-packages@5.2.2:
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
node-mock-http@1.0.4:
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
ofetch@1.5.1:
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -2000,6 +2048,9 @@ packages:
queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@@ -2039,6 +2090,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
readdirp@5.0.0:
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
engines: {node: '>= 20.19.0'}
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
@@ -2254,6 +2309,12 @@ packages:
tx2@1.0.5:
resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==}
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -2264,6 +2325,68 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unstorage@1.17.4:
resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==}
peerDependencies:
'@azure/app-configuration': ^1.8.0
'@azure/cosmos': ^4.2.0
'@azure/data-tables': ^13.3.0
'@azure/identity': ^4.6.0
'@azure/keyvault-secrets': ^4.9.0
'@azure/storage-blob': ^12.26.0
'@capacitor/preferences': ^6 || ^7 || ^8
'@deno/kv': '>=0.9.0'
'@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
'@planetscale/database': ^1.19.0
'@upstash/redis': ^1.34.3
'@vercel/blob': '>=0.27.1'
'@vercel/functions': ^2.2.12 || ^3.0.0
'@vercel/kv': ^1 || ^2 || ^3
aws4fetch: ^1.0.20
db0: '>=0.2.1'
idb-keyval: ^6.2.1
ioredis: ^5.4.2
uploadthing: ^7.4.4
peerDependenciesMeta:
'@azure/app-configuration':
optional: true
'@azure/cosmos':
optional: true
'@azure/data-tables':
optional: true
'@azure/identity':
optional: true
'@azure/keyvault-secrets':
optional: true
'@azure/storage-blob':
optional: true
'@capacitor/preferences':
optional: true
'@deno/kv':
optional: true
'@netlify/blobs':
optional: true
'@planetscale/database':
optional: true
'@upstash/redis':
optional: true
'@vercel/blob':
optional: true
'@vercel/functions':
optional: true
'@vercel/kv':
optional: true
aws4fetch:
optional: true
db0:
optional: true
idb-keyval:
optional: true
ioredis:
optional: true
uploadthing:
optional: true
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -3034,6 +3157,38 @@ snapshots:
'@kevisual/auth@2.0.3': {}
'@kevisual/cnb@0.0.28(dotenv@17.3.1)(ioredis@5.9.3)':
dependencies:
'@kevisual/query': 0.0.49
'@kevisual/router': 0.0.80
'@kevisual/use-config': 1.0.30(dotenv@17.3.1)
es-toolkit: 1.44.0
nanoid: 5.1.6
unstorage: 1.17.4(ioredis@5.9.3)
ws: '@kevisual/ws@8.19.0'
zod: 4.3.6
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
- '@azure/data-tables'
- '@azure/identity'
- '@azure/keyvault-secrets'
- '@azure/storage-blob'
- '@capacitor/preferences'
- '@deno/kv'
- '@netlify/blobs'
- '@planetscale/database'
- '@upstash/redis'
- '@vercel/blob'
- '@vercel/functions'
- '@vercel/kv'
- aws4fetch
- db0
- dotenv
- idb-keyval
- ioredis
- uploadthing
'@kevisual/context@0.0.4': {}
'@kevisual/context@0.0.6': {}
@@ -3080,6 +3235,10 @@ snapshots:
dependencies:
es-toolkit: 1.44.0
'@kevisual/router@0.0.83':
dependencies:
es-toolkit: 1.44.0
'@kevisual/types@0.0.12': {}
'@kevisual/use-config@1.0.30(dotenv@17.3.1)':
@@ -3089,6 +3248,8 @@ snapshots:
'@kevisual/ws@8.0.0': {}
'@kevisual/ws@8.19.0': {}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
@@ -3731,6 +3892,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
chokidar@5.0.0:
dependencies:
readdirp: 5.0.0
chownr@1.1.4:
optional: true
@@ -3756,6 +3921,8 @@ snapshots:
normalize-path: 3.0.0
readable-stream: 4.5.2
cookie-es@1.2.2: {}
core-util-is@1.0.3: {}
crc-32@1.2.2: {}
@@ -3777,6 +3944,10 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crossws@0.3.5:
dependencies:
uncrypto: 0.1.3
crypto-js@4.2.0: {}
culvert@0.1.2: {}
@@ -3809,6 +3980,8 @@ snapshots:
deep-extend@0.6.0:
optional: true
defu@6.1.4: {}
degenerator@5.0.1:
dependencies:
ast-types: 0.13.4
@@ -3819,6 +3992,8 @@ snapshots:
depd@2.0.0: {}
destr@2.0.5: {}
detect-libc@2.1.2:
optional: true
@@ -4053,6 +4228,18 @@ snapshots:
graceful-fs@4.2.11: {}
h3@1.15.5:
dependencies:
cookie-es: 1.2.2
crossws: 0.3.5
defu: 6.1.4
destr: 2.0.5
iron-webcrypto: 1.2.1
node-mock-http: 1.0.4
radix3: 1.1.2
ufo: 1.6.3
uncrypto: 0.1.3
has-flag@4.0.0: {}
hasown@2.0.2:
@@ -4124,6 +4311,8 @@ snapshots:
jsbn: 1.1.0
sprintf-js: 1.1.3
iron-webcrypto@1.2.1: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@@ -4226,6 +4415,8 @@ snapshots:
lru-cache@10.4.3: {}
lru-cache@11.2.6: {}
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
@@ -4305,13 +4496,23 @@ snapshots:
node-abort-controller@3.1.1: {}
node-fetch-native@1.6.7: {}
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.1.2
optional: true
node-mock-http@1.0.4: {}
normalize-path@3.0.0: {}
ofetch@1.5.1:
dependencies:
destr: 2.0.5
node-fetch-native: 1.6.7
ufo: 1.6.3
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -4537,6 +4738,8 @@ snapshots:
queue-tick@1.0.1: {}
radix3@1.1.2: {}
range-parser@1.2.1: {}
rc@1.2.8:
@@ -4591,6 +4794,8 @@ snapshots:
dependencies:
picomatch: 4.0.2
readdirp@5.0.0: {}
redis-errors@1.2.0: {}
redis-parser@3.0.0:
@@ -4813,12 +5018,29 @@ snapshots:
json-stringify-safe: 5.0.1
optional: true
ufo@1.6.3: {}
uncrypto@0.1.3: {}
undici-types@7.16.0: {}
undici-types@7.18.2: {}
universalify@2.0.1: {}
unstorage@1.17.4(ioredis@5.9.3):
dependencies:
anymatch: 3.1.3
chokidar: 5.0.0
destr: 2.0.5
h3: 1.15.5
lru-cache: 11.2.6
node-fetch-native: 1.6.7
ofetch: 1.5.1
ufo: 1.6.3
optionalDependencies:
ioredis: 5.9.3
util-deprecate@1.0.2: {}
uuid@11.1.0: {}