Added token authentication, organization module. Moved server bootstrapping code to server.ts file

This commit is contained in:
2024-12-19 21:49:54 +05:30
parent 970a972b11
commit 4b49c43a0c
16 changed files with 652 additions and 22 deletions

View File

@@ -12,6 +12,7 @@
"license": "ISC",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"bcrypt": "^5.1.1",
"fastify": "^5.2.0",
"fastify-type-provider-zod": "^4.0.2",
"fastify-zod": "^1.4.0",
@@ -19,6 +20,7 @@
"zod": "^3.24.1"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/node": "^22.10.2",
"typescript": "^5.7.2"
}

389
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
bcrypt:
specifier: ^5.1.1
version: 5.1.1
fastify:
specifier: ^5.2.0
version: 5.2.0
@@ -27,6 +30,9 @@ importers:
specifier: ^3.24.1
version: 3.24.1
devDependencies:
'@types/bcrypt':
specifier: ^5.0.2
version: 5.0.2
'@types/node':
specifier: ^22.10.2
version: 22.10.2
@@ -68,6 +74,10 @@ packages:
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
engines: {node: '>=8'}
'@mapbox/node-pre-gyp@1.0.11':
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
'@mongodb-js/saslprep@1.1.9':
resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==}
@@ -78,6 +88,9 @@ packages:
'@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
'@types/bcrypt@5.0.2':
resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==}
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
@@ -90,9 +103,16 @@ packages:
'@types/whatwg-url@11.0.5':
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
abstract-logging@2.0.1:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
@@ -104,6 +124,18 @@ packages:
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -117,6 +149,13 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
bcrypt@5.1.1:
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
engines: {node: '>= 10.0.0'}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
@@ -133,6 +172,20 @@ packages:
change-case@4.1.2:
resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
@@ -153,13 +206,23 @@ packages:
supports-color:
optional: true
delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -213,14 +276,30 @@ packages:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
deprecated: Glob versions prior to v9 are no longer supported
has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
header-case@2.0.4:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
@@ -228,6 +307,10 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -239,6 +322,10 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
@@ -263,6 +350,10 @@ packages:
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
@@ -271,10 +362,30 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
mongodb-connection-string-url@3.0.1:
resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==}
@@ -323,6 +434,31 @@ packages:
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
on-exit-leak-free@2.1.2:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
@@ -346,6 +482,10 @@ packages:
path-case@3.0.4:
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
pino-abstract-transport@2.0.0:
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
@@ -370,6 +510,10 @@ packages:
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
@@ -389,6 +533,11 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -402,6 +551,10 @@ packages:
secure-json-parse@3.0.1:
resolution: {integrity: sha512-9QR7G96th4QJ2+dJwvZB+JoXyt8PN+DbEjOr6kL2/JU4KH8Eb2sFdU+gt8EDdzWDWoWH0uocDdfCoFzdVSixUA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
@@ -410,6 +563,9 @@ packages:
sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
@@ -419,6 +575,9 @@ packages:
sift@17.1.3:
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
@@ -436,6 +595,21 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@@ -447,6 +621,9 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
tr46@4.1.1:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
engines: {node: '>=14'}
@@ -471,6 +648,12 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
@@ -479,9 +662,18 @@ packages:
resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==}
engines: {node: '>=16'}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.6.1:
resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==}
engines: {node: '>= 14'}
@@ -556,6 +748,21 @@ snapshots:
'@lukeed/ms@2.0.2': {}
'@mapbox/node-pre-gyp@1.0.11':
dependencies:
detect-libc: 2.0.3
https-proxy-agent: 5.0.1
make-dir: 3.1.0
node-fetch: 2.7.0
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.6.3
tar: 6.2.1
transitivePeerDependencies:
- encoding
- supports-color
'@mongodb-js/saslprep@1.1.9':
dependencies:
sparse-bitfield: 3.0.3
@@ -566,6 +773,10 @@ snapshots:
dependencies:
'@noble/hashes': 1.6.1
'@types/bcrypt@5.0.2':
dependencies:
'@types/node': 22.10.2
'@types/js-yaml@4.0.9': {}
'@types/node@22.10.2':
@@ -578,8 +789,16 @@ snapshots:
dependencies:
'@types/webidl-conversions': 7.0.3
abbrev@1.1.1: {}
abstract-logging@2.0.1: {}
agent-base@6.0.2:
dependencies:
debug: 4.4.0
transitivePeerDependencies:
- supports-color
ajv-formats@3.0.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
@@ -591,6 +810,15 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
ansi-regex@5.0.1: {}
aproba@2.0.0: {}
are-we-there-yet@2.0.0:
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
argparse@2.0.1: {}
atomic-sleep@1.0.0: {}
@@ -602,6 +830,19 @@ snapshots:
balanced-match@1.0.2: {}
bcrypt@5.1.1:
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
node-addon-api: 5.1.0
transitivePeerDependencies:
- encoding
- supports-color
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
@@ -634,6 +875,14 @@ snapshots:
snake-case: 3.0.4
tslib: 2.8.1
chownr@2.0.0: {}
color-support@1.1.3: {}
concat-map@0.0.1: {}
console-control-strings@1.1.0: {}
constant-case@3.0.4:
dependencies:
no-case: 3.0.4
@@ -650,13 +899,19 @@ snapshots:
dependencies:
ms: 2.1.3
delegates@1.0.0: {}
depd@2.0.0: {}
detect-libc@2.0.3: {}
dot-case@3.0.4:
dependencies:
no-case: 3.0.4
tslib: 2.8.1
emoji-regex@8.0.0: {}
escape-html@1.0.3: {}
fast-decode-uri-component@1.0.1: {}
@@ -737,8 +992,33 @@ snapshots:
forwarded@0.2.0: {}
fs-minipass@2.1.0:
dependencies:
minipass: 3.3.6
fs.realpath@1.0.0: {}
gauge@3.0.2:
dependencies:
aproba: 2.0.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
glob@8.1.0:
dependencies:
fs.realpath: 1.0.0
@@ -747,6 +1027,8 @@ snapshots:
minimatch: 5.1.6
once: 1.4.0
has-unicode@2.0.1: {}
header-case@2.0.4:
dependencies:
capital-case: 1.0.4
@@ -760,6 +1042,13 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.0
transitivePeerDependencies:
- supports-color
inflight@1.0.6:
dependencies:
once: 1.4.0
@@ -769,6 +1058,8 @@ snapshots:
ipaddr.js@1.9.1: {}
is-fullwidth-code-point@3.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@@ -799,14 +1090,35 @@ snapshots:
dependencies:
tslib: 2.8.1
make-dir@3.1.0:
dependencies:
semver: 6.3.1
memory-pager@1.5.0: {}
mime@3.0.0: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
minipass@3.3.6:
dependencies:
yallist: 4.0.0
minipass@5.0.0: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
mkdirp@1.0.4: {}
mongodb-connection-string-url@3.0.1:
dependencies:
'@types/whatwg-url': 11.0.5
@@ -852,6 +1164,25 @@ snapshots:
lower-case: 2.0.2
tslib: 2.8.1
node-addon-api@5.1.0: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
nopt@5.0.0:
dependencies:
abbrev: 1.1.1
npmlog@5.0.1:
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
object-assign@4.1.1: {}
on-exit-leak-free@2.1.2: {}
once@1.4.0:
@@ -879,6 +1210,8 @@ snapshots:
dot-case: 3.0.4
tslib: 2.8.1
path-is-absolute@1.0.1: {}
pino-abstract-transport@2.0.0:
dependencies:
split2: 4.2.0
@@ -910,6 +1243,12 @@ snapshots:
quick-format-unescaped@4.0.4: {}
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
real-require@0.2.0: {}
require-from-string@2.0.2: {}
@@ -920,6 +1259,10 @@ snapshots:
rfdc@1.4.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
safe-buffer@5.2.1: {}
safe-regex2@4.0.0:
@@ -930,6 +1273,8 @@ snapshots:
secure-json-parse@3.0.1: {}
semver@6.3.1: {}
semver@7.6.3: {}
sentence-case@3.0.4:
@@ -938,12 +1283,16 @@ snapshots:
tslib: 2.8.1
upper-case-first: 2.0.2
set-blocking@2.0.0: {}
set-cookie-parser@2.7.1: {}
setprototypeof@1.2.0: {}
sift@17.1.3: {}
signal-exit@3.0.7: {}
snake-case@3.0.4:
dependencies:
dot-case: 3.0.4
@@ -961,6 +1310,29 @@ snapshots:
statuses@2.0.1: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
tar@6.2.1:
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
@@ -969,6 +1341,8 @@ snapshots:
toidentifier@1.0.1: {}
tr46@0.0.3: {}
tr46@4.1.1:
dependencies:
punycode: 2.3.1
@@ -991,6 +1365,10 @@ snapshots:
dependencies:
punycode: 2.3.1
util-deprecate@1.0.2: {}
webidl-conversions@3.0.1: {}
webidl-conversions@7.0.0: {}
whatwg-url@13.0.0:
@@ -998,8 +1376,19 @@ snapshots:
tr46: 4.1.1
webidl-conversions: 7.0.0
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
wide-align@1.1.5:
dependencies:
string-width: 4.2.3
wrappy@1.0.2: {}
yallist@4.0.0: {}
yaml@2.6.1: {}
yocto-queue@0.1.0: {}

26
src/auth.ts Normal file
View File

@@ -0,0 +1,26 @@
import bcrypt from "bcrypt";
import { FastifyReply, FastifyRequest } from "fastify";
import { getToken } from "./tokens/token.service";
import { Claim } from "./utils/claims";
export type AuthenticatedUser = {
userId?: String;
claims: Array<Claim>;
};
export async function authHandler(req: FastifyRequest, res: FastifyReply) {
if (!req.headers.authorization) return res.code(401).send();
const [tokenId, token] = req.headers.authorization.split(" ")[1].split(".");
if (!tokenId || !token) return res.code(401).send({ error: "invalid token" });
const tokenInDb = await getToken(tokenId);
if (tokenInDb === null) return res.code(401).send({ error: "invalid token" });
const valid = await bcrypt.compare(token, tokenInDb.hash);
if (!valid) return res.code(401).send({ error: "invalid token" });
req.user = {
claims: tokenInDb.claims as Array<Claim>,
};
}

View File

@@ -1,28 +1,10 @@
import mongoose from "mongoose";
import fastify from "fastify";
import routes from "./routes";
import { userSchemas } from "./user/user.schema";
import { orgSchemas } from "./organization/organization.schema";
import { errorHandler } from "./utils/errors";
const app = fastify({ logger: true });
app.get("/health", (req, res) => {
return { status: "OK" };
});
app.register(routes, { prefix: "/api/v1" });
app.setErrorHandler(errorHandler);
import app from "./server";
(async () => {
const PORT = parseInt(process.env.PORT ?? "8000");
const DB_URI = process.env.DB_URI ?? "";
for (const schema of [...userSchemas, ...orgSchemas]) {
app.addSchema(schema);
}
await mongoose.connect(DB_URI);
await app.listen({ port: PORT });
})().catch((err) => {

View File

@@ -1,6 +1,6 @@
import { FastifyRequest, FastifyReply } from "fastify";
import { CreateOrgInput } from "./organization.schema";
import { createOrg } from "./organization.service";
import { createOrg, getOrg } from "./organization.service";
export async function createOrgHandler(
req: FastifyRequest<{ Body: CreateOrgInput }>,
@@ -15,3 +15,20 @@ export async function createOrgHandler(
return err;
}
}
export async function getOrgHandler(
req: FastifyRequest<{ Params: { orgId: string } }>,
res: FastifyReply
) {
const { orgId } = req.params;
try {
const org = await getOrg(orgId);
if (org === null)
return res.code(404).send({ error: "resource not found" });
return res.code(200).send(org);
} catch (err) {
return err;
}
}

View File

@@ -1,6 +1,6 @@
import { FastifyInstance } from "fastify";
import { $org } from "./organization.schema";
import { createOrgHandler } from "./organization.controller";
import { createOrgHandler, getOrgHandler } from "./organization.controller";
export default function organizationRoutes(fastify: FastifyInstance) {
fastify.post(
@@ -15,4 +15,19 @@ export default function organizationRoutes(fastify: FastifyInstance) {
},
createOrgHandler
);
fastify.get(
"/:orgId",
{
schema: {
params: {
type: "object",
properties: {
orgId: { type: "string" },
},
},
},
},
getOrgHandler
);
}

View File

@@ -11,3 +11,7 @@ export async function createOrg(input: CreateOrgInput) {
return org;
}
export async function getOrg(orgId: string) {
return await orgModel.findOne({ pid: orgId });
}

View File

@@ -1,8 +1,10 @@
import { FastifyInstance } from "fastify";
import userRoutes from "./user/user.route";
import organizationRoutes from "./organization/organization.route";
import { tokenRoutes } from "./tokens/token.route";
export default async function routes(fastify: FastifyInstance) {
fastify.register(userRoutes, { prefix: "/users" });
fastify.register(organizationRoutes, { prefix: "/orgs" });
fastify.register(tokenRoutes, { prefix: "/tokens" });
}

31
src/server.ts Normal file
View File

@@ -0,0 +1,31 @@
import mongoose from "mongoose";
import fastify from "fastify";
import routes from "./routes";
import { userSchemas } from "./user/user.schema";
import { orgSchemas } from "./organization/organization.schema";
import { tokenSchemas } from "./tokens/token.schema";
import { errorHandler } from "./utils/errors";
import { AuthenticatedUser, authHandler } from "./auth";
declare module "fastify" {
export interface FastifyRequest {
user: AuthenticatedUser;
}
}
const app = fastify({ logger: true });
app.get("/health", (req, res) => {
return { status: "OK" };
});
app.register(routes, { prefix: "/api/v1" });
app.setErrorHandler(errorHandler);
app.addHook("onRequest", authHandler);
for (const schema of [...userSchemas, ...orgSchemas, ...tokenSchemas]) {
app.addSchema(schema);
}
export default app;

View File

@@ -0,0 +1,33 @@
import { FastifyReply, FastifyRequest } from "fastify";
import { CreateTokenInput } from "./token.schema";
import { createToken, getToken } from "./token.service";
export async function createTokenHandler(
req: FastifyRequest<{ Body: CreateTokenInput }>,
res: FastifyReply
) {
const input = req.body;
try {
const result = await createToken(input);
return res.code(201).send(result);
} catch (err) {
return err;
}
}
export async function getTokenHandler(
req: FastifyRequest<{ Params: { tokenId: string } }>,
res: FastifyReply
) {
const { tokenId } = req.params;
try {
const token = await getToken(tokenId);
if (token === null) return res.code(404).send();
return res.code(200).send(token);
} catch (err) {
return err;
}
}

24
src/tokens/token.route.ts Normal file
View File

@@ -0,0 +1,24 @@
import { FastifyInstance } from "fastify";
import { createTokenHandler, getTokenHandler } from "./token.controller";
import { $token } from "./token.schema";
export async function tokenRoutes(fastify: FastifyInstance) {
fastify.post(
"/",
{
schema: {
body: $token("createTokenInput"),
response: {
201: $token("createTokenResponse"),
},
},
},
createTokenHandler
);
fastify.get(
"/:tokenId",
{ schema: { response: { 200: $token("getTokenResponse") } } },
getTokenHandler
);
}

View File

@@ -0,0 +1,52 @@
import { buildJsonSchemas } from "fastify-zod";
import mongoose from "mongoose";
import { z } from "zod";
import { Claim } from "../utils/claims";
export const tokenModel = mongoose.model(
"token",
new mongoose.Schema({
tenantId: String,
pid: {
type: String,
unique: true,
},
name: String,
claims: [],
userId: mongoose.Types.ObjectId,
hash: { type: String, required: true },
createdAt: Date,
})
);
const tokenCore = {
name: z.string().max(30),
claims: z.array(z.string()).nonempty(),
};
const createTokenInput = z.object({
...tokenCore,
});
const createTokenResponse = z.object({
pid: z.string(),
token: z.string(),
...tokenCore,
});
const getTokenResponse = z.object({
pid: z.string(),
name: z.string(),
createdAt: z.string(),
});
export type CreateTokenInput = z.infer<typeof createTokenInput>;
export const { schemas: tokenSchemas, $ref: $token } = buildJsonSchemas(
{
createTokenInput,
createTokenResponse,
getTokenResponse,
},
{ $id: "token" }
);

View File

@@ -0,0 +1,27 @@
import bcrypt from "bcrypt";
import { generateId, generateToken } from "../utils/id";
import { CreateTokenInput, tokenModel } from "./token.schema";
export async function createToken(input: CreateTokenInput) {
const tokenId = generateId();
const newToken = await generateToken();
const tokenHash = await bcrypt.hash(newToken, 10);
const tokenInDb = await tokenModel.create({
pid: tokenId,
hash: tokenHash,
createdAt: new Date(),
...input,
});
return {
pid: tokenInDb.pid,
name: tokenInDb.name,
claims: tokenInDb.claims,
token: `${tokenInDb.pid}.${newToken}`,
};
}
export async function getToken(tokenId: string) {
return await tokenModel.findOne({ pid: tokenId });
}

View File

@@ -26,7 +26,8 @@ export async function getUserHandler(
try {
const user = await getUser(userId);
if (user == null) return res.code(404).send({ error: "user not found" });
if (user == null)
return res.code(404).send({ error: "resource not found" });
return res.code(200).send(user);
} catch (err) {

15
src/utils/claims.ts Normal file
View File

@@ -0,0 +1,15 @@
export type Claim =
| "user:read"
| "user:write"
| "user:delete"
| "org:read"
| "org:write"
| "org:delete"
| "permit:read"
| "permit:write"
| "permit:delete"
| "file:upload"
| "file:download"
| "api:read"
| "api:write"
| "api:delete";

View File

@@ -1,4 +1,5 @@
import { init } from "@paralleldrive/cuid2";
import crypto from "crypto";
const id = init({
length: 15,
@@ -7,3 +8,12 @@ const id = init({
export function generateId(perfix?: string) {
return perfix ? perfix + id() : id();
}
export async function generateToken(): Promise<string> {
return new Promise((resolve, reject) => {
crypto.generateKey("aes", { length: 256 }, (err, key) => {
if (err) reject(err);
resolve(key.export().toString("base64url"));
});
});
}