diff --git a/package.json b/package.json index 5096240..e4c087d 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index daec7e1..8462087 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..b16e59b --- /dev/null +++ b/src/auth.ts @@ -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; +}; + +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, + }; +} diff --git a/src/index.ts b/src/index.ts index 87e988e..41ec9c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) => { diff --git a/src/organization/organization.controller.ts b/src/organization/organization.controller.ts index b3b9b16..92ef61e 100644 --- a/src/organization/organization.controller.ts +++ b/src/organization/organization.controller.ts @@ -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; + } +} diff --git a/src/organization/organization.route.ts b/src/organization/organization.route.ts index 413ead1..7bf2a69 100644 --- a/src/organization/organization.route.ts +++ b/src/organization/organization.route.ts @@ -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 + ); } diff --git a/src/organization/organization.service.ts b/src/organization/organization.service.ts index 8882138..5cff0e6 100644 --- a/src/organization/organization.service.ts +++ b/src/organization/organization.service.ts @@ -11,3 +11,7 @@ export async function createOrg(input: CreateOrgInput) { return org; } + +export async function getOrg(orgId: string) { + return await orgModel.findOne({ pid: orgId }); +} diff --git a/src/routes.ts b/src/routes.ts index b78a533..5e60b0c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -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" }); } diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..93f2678 --- /dev/null +++ b/src/server.ts @@ -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; diff --git a/src/tokens/token.controller.ts b/src/tokens/token.controller.ts new file mode 100644 index 0000000..5604eb5 --- /dev/null +++ b/src/tokens/token.controller.ts @@ -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; + } +} diff --git a/src/tokens/token.route.ts b/src/tokens/token.route.ts new file mode 100644 index 0000000..9bacf10 --- /dev/null +++ b/src/tokens/token.route.ts @@ -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 + ); +} diff --git a/src/tokens/token.schema.ts b/src/tokens/token.schema.ts new file mode 100644 index 0000000..4fb8677 --- /dev/null +++ b/src/tokens/token.schema.ts @@ -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; + +export const { schemas: tokenSchemas, $ref: $token } = buildJsonSchemas( + { + createTokenInput, + createTokenResponse, + getTokenResponse, + }, + { $id: "token" } +); diff --git a/src/tokens/token.service.ts b/src/tokens/token.service.ts new file mode 100644 index 0000000..abd051f --- /dev/null +++ b/src/tokens/token.service.ts @@ -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 }); +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index d21525b..fb889c7 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -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) { diff --git a/src/utils/claims.ts b/src/utils/claims.ts new file mode 100644 index 0000000..a7d8882 --- /dev/null +++ b/src/utils/claims.ts @@ -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"; diff --git a/src/utils/id.ts b/src/utils/id.ts index a9cce74..d68f997 100644 --- a/src/utils/id.ts +++ b/src/utils/id.ts @@ -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 { + return new Promise((resolve, reject) => { + crypto.generateKey("aes", { length: 256 }, (err, key) => { + if (err) reject(err); + resolve(key.export().toString("base64url")); + }); + }); +}