From 8f7eebb806d2a23f69ab7271f378591e4e9f206b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Sun, 19 Feb 2023 16:16:34 +0100 Subject: [PATCH] feat(api): API tokens for authentication Create and managed simple API tokens for access to the API from external tools. --- package-lock.json | 478 ++++++++++++------ package.json | 2 + src/auth/api-token.entity.ts | 32 ++ src/auth/api-token.repository.ts | 24 + src/auth/auth.controller.ts | 34 +- src/auth/auth.module.ts | 5 +- src/auth/auth.service.spec.ts | 5 + src/auth/auth.service.ts | 47 +- .../decorators/auth-access-token.decorator.ts | 6 +- src/auth/dto/create-api-token-request.dto.ts | 9 + src/auth/dto/revoke-api-token-request.dto.ts | 9 + src/auth/guards/auth-strategies.guard.ts | 5 +- src/auth/strategies/api-token.strategy.ts | 34 ++ src/auth/strategies/strategies.enum.ts | 1 + .../migrations/07-CreateApiTokenTable.ts | 77 +++ 15 files changed, 614 insertions(+), 154 deletions(-) create mode 100644 src/auth/api-token.entity.ts create mode 100644 src/auth/api-token.repository.ts create mode 100644 src/auth/dto/create-api-token-request.dto.ts create mode 100644 src/auth/dto/revoke-api-token-request.dto.ts create mode 100644 src/auth/strategies/api-token.strategy.ts create mode 100644 src/database/migrations/07-CreateApiTokenTable.ts diff --git a/package-lock.json b/package-lock.json index 1be219e..c5467eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "nestjs-pino": "3.1.2", "nestjs-typeorm-paginate": "4.0.3", "passport": "0.6.0", + "passport-http-bearer": "^1.0.1", "passport-jwt": "4.0.1", "passport-spotify": "2.0.0", "pg": "8.8.0", @@ -70,6 +71,7 @@ "@types/jest": "29.2.5", "@types/long": "4.0.2", "@types/node": "18.11.19", + "@types/passport-http-bearer": "^1.0.37", "@types/passport-jwt": "3.0.8", "@types/supertest": "2.0.12", "@typescript-eslint/eslint-plugin": "5.48.1", @@ -1479,12 +1481,12 @@ } }, "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.25.16" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -1583,12 +1585,12 @@ "dev": true }, "node_modules/@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", + "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1677,13 +1679,13 @@ "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@narando/nest-axios-interceptor": { @@ -2709,17 +2711,17 @@ } }, "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.9.1.tgz", + "integrity": "sha512-6/qon6tw2I8ZaJnHAQUUn4BqhTbTNRS0WP8/bA0ynaX+Uzp/DDbd0NS0Cq6TMlh8+mrlsyqDE7mO50nmv2Yvlg==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.9.1" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.4.0" + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/instrumentation": { @@ -2739,9 +2741,9 @@ } }, "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz", + "integrity": "sha512-oPQdbFDmZvjXk5ZDoBGXG8B4tSB/qW5vQunJWQMFUBp7Xe8O1ByPANueJ+Jzg58esEBegyyxZ7LRmfJr7kFcFg==", "engines": { "node": ">=14" } @@ -2838,17 +2840,17 @@ } }, "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.9.1.tgz", + "integrity": "sha512-6/qon6tw2I8ZaJnHAQUUn4BqhTbTNRS0WP8/bA0ynaX+Uzp/DDbd0NS0Cq6TMlh8+mrlsyqDE7mO50nmv2Yvlg==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.9.1" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.4.0" + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/instrumentation": { @@ -2868,9 +2870,9 @@ } }, "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz", + "integrity": "sha512-oPQdbFDmZvjXk5ZDoBGXG8B4tSB/qW5vQunJWQMFUBp7Xe8O1ByPANueJ+Jzg58esEBegyyxZ7LRmfJr7kFcFg==", "engines": { "node": ">=14" } @@ -3436,9 +3438,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sinclair/typebox": { - "version": "0.24.43", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.43.tgz", - "integrity": "sha512-1orQTvtazZmsPeBroJjysvsOQCYV2yjWlebkSY38pl5vr2tdLjEJ+LoxITlGNZaH2RE19WlAwQMkH/7C14wLfw==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "node_modules/@sinonjs/commons": { @@ -3488,6 +3490,15 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "devOptional": true }, + "node_modules/@types/accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -3546,6 +3557,12 @@ "@types/node": "*" } }, + "node_modules/@types/content-disposition": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz", + "integrity": "sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==", + "dev": true + }, "node_modules/@types/cookie-parser": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", @@ -3561,6 +3578,18 @@ "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", "dev": true }, + "node_modules/@types/cookies": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz", + "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", @@ -3624,6 +3653,18 @@ "integrity": "sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==", "dev": true }, + "node_modules/@types/http-assert": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.3.tgz", + "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3678,6 +3719,37 @@ "@types/node": "*" } }, + "node_modules/@types/keygrip": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", + "dev": true + }, + "node_modules/@types/koa": { + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.5.tgz", + "integrity": "sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA==", + "dev": true, + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", + "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -3708,6 +3780,17 @@ "@types/express": "*" } }, + "node_modules/@types/passport-http-bearer": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/@types/passport-http-bearer/-/passport-http-bearer-1.0.37.tgz", + "integrity": "sha512-/2Z28LfgY7kP/GO75os+feTP+//qHfpYn3V7sWAl0kwNwyDT1eGgjO30OU+Lown00ogSee+fea8a0+fr/UpTXw==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/koa": "*", + "@types/passport": "*" + } + }, "node_modules/@types/passport-jwt": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", @@ -5348,18 +5431,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, "node_modules/cookie": { @@ -7114,9 +7188,9 @@ "dev": true }, "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -7293,9 +7367,9 @@ } }, "node_modules/help-me/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -7312,9 +7386,9 @@ } }, "node_modules/help-me/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -8768,12 +8842,12 @@ } }, "node_modules/jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", + "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -8869,13 +8943,13 @@ } }, "node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -10027,6 +10101,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-http-bearer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz", + "integrity": "sha512-SELQM+dOTuMigr9yu8Wo4Fm3ciFfkMq5h/ZQ8ffi4ELgZrX1xh9PlglqZdcUZ1upzJD/whVyt+YWF62s3U6Ipw==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", @@ -10316,9 +10401,9 @@ } }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -10390,9 +10475,9 @@ } }, "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "dependencies": { "abort-controller": "^3.0.0", @@ -12904,17 +12989,17 @@ } }, "node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz", + "integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" @@ -12928,6 +13013,19 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -14016,12 +14114,12 @@ } }, "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.25.16" } }, "@jest/source-map": { @@ -14101,12 +14199,12 @@ } }, "@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", + "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -14178,13 +14276,13 @@ "devOptional": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "@narando/nest-axios-interceptor": { @@ -14805,11 +14903,11 @@ }, "dependencies": { "@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.9.1.tgz", + "integrity": "sha512-6/qon6tw2I8ZaJnHAQUUn4BqhTbTNRS0WP8/bA0ynaX+Uzp/DDbd0NS0Cq6TMlh8+mrlsyqDE7mO50nmv2Yvlg==", "requires": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.9.1" } }, "@opentelemetry/instrumentation": { @@ -14823,9 +14921,9 @@ } }, "@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz", + "integrity": "sha512-oPQdbFDmZvjXk5ZDoBGXG8B4tSB/qW5vQunJWQMFUBp7Xe8O1ByPANueJ+Jzg58esEBegyyxZ7LRmfJr7kFcFg==" }, "@types/express": { "version": "4.17.13", @@ -14895,11 +14993,11 @@ }, "dependencies": { "@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.9.1.tgz", + "integrity": "sha512-6/qon6tw2I8ZaJnHAQUUn4BqhTbTNRS0WP8/bA0ynaX+Uzp/DDbd0NS0Cq6TMlh8+mrlsyqDE7mO50nmv2Yvlg==", "requires": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.9.1" } }, "@opentelemetry/instrumentation": { @@ -14913,9 +15011,9 @@ } }, "@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz", + "integrity": "sha512-oPQdbFDmZvjXk5ZDoBGXG8B4tSB/qW5vQunJWQMFUBp7Xe8O1ByPANueJ+Jzg58esEBegyyxZ7LRmfJr7kFcFg==" } } }, @@ -15328,9 +15426,9 @@ } }, "@sinclair/typebox": { - "version": "0.24.43", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.43.tgz", - "integrity": "sha512-1orQTvtazZmsPeBroJjysvsOQCYV2yjWlebkSY38pl5vr2tdLjEJ+LoxITlGNZaH2RE19WlAwQMkH/7C14wLfw==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "@sinonjs/commons": { @@ -15380,6 +15478,15 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "devOptional": true }, + "@types/accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -15438,6 +15545,12 @@ "@types/node": "*" } }, + "@types/content-disposition": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz", + "integrity": "sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==", + "dev": true + }, "@types/cookie-parser": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", @@ -15453,6 +15566,18 @@ "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", "dev": true }, + "@types/cookies": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz", + "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "@types/eslint": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", @@ -15516,6 +15641,18 @@ "integrity": "sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==", "dev": true }, + "@types/http-assert": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.3.tgz", + "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", + "dev": true + }, + "@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -15570,6 +15707,37 @@ "@types/node": "*" } }, + "@types/keygrip": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", + "dev": true + }, + "@types/koa": { + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.5.tgz", + "integrity": "sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA==", + "dev": true, + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "@types/koa-compose": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", + "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, "@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -15600,6 +15768,17 @@ "@types/express": "*" } }, + "@types/passport-http-bearer": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/@types/passport-http-bearer/-/passport-http-bearer-1.0.37.tgz", + "integrity": "sha512-/2Z28LfgY7kP/GO75os+feTP+//qHfpYn3V7sWAl0kwNwyDT1eGgjO30OU+Lown00ogSee+fea8a0+fr/UpTXw==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/koa": "*", + "@types/passport": "*" + } + }, "@types/passport-jwt": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", @@ -16834,21 +17013,10 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "cookie": { "version": "0.4.1", @@ -18201,9 +18369,9 @@ "dev": true }, "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -18327,9 +18495,9 @@ } }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -18340,9 +18508,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -19404,12 +19572,12 @@ } }, "jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", + "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -19484,13 +19652,13 @@ } }, "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -20364,6 +20532,14 @@ "utils-merge": "^1.0.1" } }, + "passport-http-bearer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz", + "integrity": "sha512-SELQM+dOTuMigr9yu8Wo4Fm3ciFfkMq5h/ZQ8ffi4ELgZrX1xh9PlglqZdcUZ1upzJD/whVyt+YWF62s3U6Ipw==", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", @@ -20574,9 +20750,9 @@ } }, "readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "requires": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -20630,9 +20806,9 @@ } }, "readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dev": true, "requires": { "abort-controller": "^3.0.0", @@ -22390,17 +22566,29 @@ "dev": true }, "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz", + "integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==", "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + } } }, "yargs-parser": { diff --git a/package.json b/package.json index 5a06257..a2d8ede 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "nestjs-pino": "3.1.2", "nestjs-typeorm-paginate": "4.0.3", "passport": "0.6.0", + "passport-http-bearer": "^1.0.1", "passport-jwt": "4.0.1", "passport-spotify": "2.0.0", "pg": "8.8.0", @@ -87,6 +88,7 @@ "@types/jest": "29.2.5", "@types/long": "4.0.2", "@types/node": "18.11.19", + "@types/passport-http-bearer": "^1.0.37", "@types/passport-jwt": "3.0.8", "@types/supertest": "2.0.12", "@typescript-eslint/eslint-plugin": "5.48.1", diff --git a/src/auth/api-token.entity.ts b/src/auth/api-token.entity.ts new file mode 100644 index 0000000..545417c --- /dev/null +++ b/src/auth/api-token.entity.ts @@ -0,0 +1,32 @@ +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + PrimaryGeneratedColumn, +} from "typeorm"; +import { User } from "../users/user.entity"; + +@Entity() +export class ApiToken { + @PrimaryGeneratedColumn("uuid") + id: string; + + @ManyToOne(() => User, { eager: true }) + user: User; + + @Column() + description: string; + + @Column({ unique: true }) + token: string; + + @CreateDateColumn() + createdAt: Date; + + @Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" }) + lastUsedAt: Date; + + @Column({ type: "timestamp", nullable: true }) + revokedAt: Date | null; +} diff --git a/src/auth/api-token.repository.ts b/src/auth/api-token.repository.ts new file mode 100644 index 0000000..257eda6 --- /dev/null +++ b/src/auth/api-token.repository.ts @@ -0,0 +1,24 @@ +/* eslint-disable max-classes-per-file */ +import { Repository, SelectQueryBuilder } from "typeorm"; +import { EntityRepository } from "../database/entity-repository"; +import { User } from "../users/user.entity"; +import { ApiToken } from "./api-token.entity"; + +export class ApiTokenScopes extends SelectQueryBuilder { + /** + * `byUser` scopes the query to ApiTokens created by the user. + * @param currentUser + */ + byUser(currentUser: User): this { + return this.andWhere(`token."userId" = :userID`, { + userID: currentUser.id, + }); + } +} + +@EntityRepository(ApiToken) +export class ApiTokenRepository extends Repository { + get scoped(): ApiTokenScopes { + return new ApiTokenScopes(this.createQueryBuilder("token")); + } +} diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 60e2c70..840a3e8 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,19 +1,25 @@ import { + Body, Controller, + Delete, Get, Post, Res, UseFilters, UseGuards, } from "@nestjs/common"; -import { ApiTags } from "@nestjs/swagger"; +import { ApiBody, ApiTags } from "@nestjs/swagger"; import type { Response } from "express"; import { User } from "../users/user.entity"; +import { ApiToken } from "./api-token.entity"; import { AuthSession } from "./auth-session.entity"; import { AuthService } from "./auth.service"; import { COOKIE_REFRESH_TOKEN } from "./constants"; +import { AuthAccessToken } from "./decorators/auth-access-token.decorator"; import { ReqUser } from "./decorators/req-user.decorator"; +import { CreateApiTokenRequestDto } from "./dto/create-api-token-request.dto"; import { RefreshAccessTokenResponseDto } from "./dto/refresh-access-token-response.dto"; +import { RevokeApiTokenRequestDto } from "./dto/revoke-api-token-request.dto"; import { RefreshTokenAuthGuard, SpotifyAuthGuard, @@ -55,4 +61,30 @@ export class AuthController { return { accessToken }; } + + @Post("api-tokens") + @ApiBody({ type: CreateApiTokenRequestDto }) + @AuthAccessToken() + async createApiToken( + @ReqUser() user: User, + @Body("description") description: string + ): Promise { + return this.authService.createApiToken(user, description); + } + + @Get("api-tokens") + @AuthAccessToken() + async listApiTokens(@ReqUser() user: User): Promise { + return this.authService.listApiTokens(user); + } + + // This endpoint does not validate that the token belongs to the logged in user. + // Once the token is known, it does not matter which account makes the actual + // request to revoke it. + @Delete("api-tokens") + @ApiBody({ type: RevokeApiTokenRequestDto }) + @AuthAccessToken() + async revokeApiToken(@Body("token") token: string): Promise { + return this.authService.revokeApiToken(token); + } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 054880d..319faf2 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -5,16 +5,18 @@ import { PassportModule } from "@nestjs/passport"; import { CookieParserMiddleware } from "../cookie-parser"; import { TypeOrmRepositoryModule } from "../database/entity-repository/typeorm-repository.module"; import { UsersModule } from "../users/users.module"; +import { ApiTokenRepository } from "./api-token.repository"; import { AuthSessionRepository } from "./auth-session.repository"; import { AuthController } from "./auth.controller"; import { AuthService } from "./auth.service"; import { AccessTokenStrategy } from "./strategies/access-token.strategy"; +import { ApiTokenStrategy } from "./strategies/api-token.strategy"; import { RefreshTokenStrategy } from "./strategies/refresh-token.strategy"; import { SpotifyStrategy } from "./strategies/spotify.strategy"; @Module({ imports: [ - TypeOrmRepositoryModule.for([AuthSessionRepository]), + TypeOrmRepositoryModule.for([AuthSessionRepository, ApiTokenRepository]), PassportModule.register({ defaultStrategy: "jwt" }), JwtModule.registerAsync({ useFactory: (config: ConfigService) => ({ @@ -33,6 +35,7 @@ import { SpotifyStrategy } from "./strategies/spotify.strategy"; SpotifyStrategy, AccessTokenStrategy, RefreshTokenStrategy, + ApiTokenStrategy, ], exports: [PassportModule], controllers: [AuthController], diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index e5f7f7d..2b1d725 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -4,6 +4,7 @@ import { JwtService } from "@nestjs/jwt"; import { Test, TestingModule } from "@nestjs/testing"; import { User } from "../users/user.entity"; import { UsersService } from "../users/users.service"; +import { ApiTokenRepository } from "./api-token.repository"; import { AuthSession } from "./auth-session.entity"; import { AuthSessionRepository } from "./auth-session.repository"; import { AuthService } from "./auth.service"; @@ -15,6 +16,7 @@ describe("AuthService", () => { let usersService: UsersService; let jwtService: JwtService; let authSessionRepository: AuthSessionRepository; + let apiTokenRepository: ApiTokenRepository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -27,6 +29,7 @@ describe("AuthService", () => { { provide: UsersService, useFactory: () => ({}) }, { provide: JwtService, useFactory: () => ({}) }, { provide: AuthSessionRepository, useFactory: () => ({}) }, + { provide: ApiTokenRepository, useFactory: () => ({}) }, ], }).compile(); @@ -37,6 +40,7 @@ describe("AuthService", () => { authSessionRepository = module.get( AuthSessionRepository ); + apiTokenRepository = module.get(ApiTokenRepository); }); it("should be defined", () => { @@ -45,6 +49,7 @@ describe("AuthService", () => { expect(usersService).toBeDefined(); expect(jwtService).toBeDefined(); expect(authSessionRepository).toBeDefined(); + expect(apiTokenRepository).toBeDefined(); }); describe("spotifyLogin", () => { diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 8208a3a..4984f43 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -3,9 +3,12 @@ import { ConfigService } from "@nestjs/config"; import { JwtService } from "@nestjs/jwt"; import { User } from "../users/user.entity"; import { UsersService } from "../users/users.service"; +import { ApiToken } from "./api-token.entity"; +import { ApiTokenRepository } from "./api-token.repository"; import { AuthSession } from "./auth-session.entity"; import { AuthSessionRepository } from "./auth-session.repository"; import { LoginDto } from "./dto/login.dto"; +import { randomBytes } from "crypto"; @Injectable() export class AuthService { @@ -16,7 +19,8 @@ export class AuthService { private readonly config: ConfigService, private readonly usersService: UsersService, private readonly jwtService: JwtService, - private readonly authSessionRepository: AuthSessionRepository + private readonly authSessionRepository: AuthSessionRepository, + private readonly apiTokenRepository: ApiTokenRepository ) { this.userFilter = this.config.get("SPOTIFY_USER_FILTER"); this.sessionExpirationTime = this.config.get( @@ -66,7 +70,7 @@ export class AuthService { */ private async createRefreshToken( session: AuthSession - ): Promise<{ refreshToken }> { + ): Promise<{ refreshToken: string }> { const payload = { sub: session.user.id, name: session.user.displayName, @@ -81,7 +85,9 @@ export class AuthService { return { refreshToken: token }; } - async createAccessToken(session: AuthSession): Promise<{ accessToken }> { + async createAccessToken( + session: AuthSession + ): Promise<{ accessToken: string }> { if (session.revokedAt) { throw new ForbiddenException("SessionIsRevoked"); } @@ -101,6 +107,41 @@ export class AuthService { return this.authSessionRepository.findOneBy({ id }); } + async createApiToken(user: User, description: string): Promise { + console.log("createApiToken"); + const apiToken = this.apiTokenRepository.create(); + + apiToken.user = user; + apiToken.description = description; + + // TODO demagic 20 + const tokenBuffer = await new Promise((resolve, reject) => + randomBytes(20, (err, buf) => (err ? reject(err) : resolve(buf))) + ); + apiToken.token = `lis${tokenBuffer.toString("hex")}`; + + await this.apiTokenRepository.save(apiToken); + + return apiToken; + } + + async listApiTokens(user: User): Promise { + return this.apiTokenRepository.scoped.byUser(user).getMany(); + } + + async revokeApiToken(token: string): Promise { + const apiToken = await this.findApiToken(token); + + apiToken.revokedAt = new Date(); + await this.apiTokenRepository.save(apiToken); + + return; + } + + async findApiToken(token: string): Promise { + return this.apiTokenRepository.findOneBy({ token }); + } + async findUser(id: string): Promise { return this.usersService.findById(id); } diff --git a/src/auth/decorators/auth-access-token.decorator.ts b/src/auth/decorators/auth-access-token.decorator.ts index 5fbaad7..af9d836 100644 --- a/src/auth/decorators/auth-access-token.decorator.ts +++ b/src/auth/decorators/auth-access-token.decorator.ts @@ -1,11 +1,11 @@ import { applyDecorators, UseGuards } from "@nestjs/common"; import { ApiBearerAuth, ApiUnauthorizedResponse } from "@nestjs/swagger"; -import { AccessTokenAuthGuard } from "../guards/auth-strategies.guard"; +import { ApiAuthGuard } from "../guards/auth-strategies.guard"; export function AuthAccessToken() { return applyDecorators( - UseGuards(AccessTokenAuthGuard), + UseGuards(ApiAuthGuard), ApiBearerAuth(), - ApiUnauthorizedResponse({ description: 'Unauthorized"' }) + ApiUnauthorizedResponse({ description: "Unauthorized" }) ); } diff --git a/src/auth/dto/create-api-token-request.dto.ts b/src/auth/dto/create-api-token-request.dto.ts new file mode 100644 index 0000000..ef6351e --- /dev/null +++ b/src/auth/dto/create-api-token-request.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class CreateApiTokenRequestDto { + @ApiProperty({ + description: "Opaque text field to identify the API token", + example: "My super duper token", + }) + description: string; +} diff --git a/src/auth/dto/revoke-api-token-request.dto.ts b/src/auth/dto/revoke-api-token-request.dto.ts new file mode 100644 index 0000000..a953961 --- /dev/null +++ b/src/auth/dto/revoke-api-token-request.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class RevokeApiTokenRequestDto { + @ApiProperty({ + description: "The API Token that should be revoked", + example: "lisasdasdjaksr2381asd", + }) + token: string; +} diff --git a/src/auth/guards/auth-strategies.guard.ts b/src/auth/guards/auth-strategies.guard.ts index fb0366b..ecdfccd 100644 --- a/src/auth/guards/auth-strategies.guard.ts +++ b/src/auth/guards/auth-strategies.guard.ts @@ -2,7 +2,10 @@ import { AuthGuard } from "@nestjs/passport"; import { AuthStrategy } from "../strategies/strategies.enum"; // Internal -export const AccessTokenAuthGuard = AuthGuard(AuthStrategy.AccessToken); +export const ApiAuthGuard = AuthGuard([ + AuthStrategy.AccessToken, + AuthStrategy.ApiToken, +]); export const RefreshTokenAuthGuard = AuthGuard(AuthStrategy.RefreshToken); // Auth Provider diff --git a/src/auth/strategies/api-token.strategy.ts b/src/auth/strategies/api-token.strategy.ts new file mode 100644 index 0000000..33d31db --- /dev/null +++ b/src/auth/strategies/api-token.strategy.ts @@ -0,0 +1,34 @@ +import { + Injectable, + UnauthorizedException, + ForbiddenException, +} from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy } from "passport-http-bearer"; +import { User } from "../../users/user.entity"; +import { AuthService } from "../auth.service"; +import { AuthStrategy } from "./strategies.enum"; + +@Injectable() +export class ApiTokenStrategy extends PassportStrategy( + Strategy, + AuthStrategy.ApiToken +) { + constructor(private readonly authService: AuthService) { + super(); + } + + async validate(token: string): Promise { + const apiToken = await this.authService.findApiToken(token); + + if (!apiToken) { + throw new UnauthorizedException("TokenNotFound"); + } + + if (apiToken.revokedAt) { + throw new ForbiddenException("TokenIsRevoked"); + } + + return apiToken.user; + } +} diff --git a/src/auth/strategies/strategies.enum.ts b/src/auth/strategies/strategies.enum.ts index cf19b9d..b3d27f6 100644 --- a/src/auth/strategies/strategies.enum.ts +++ b/src/auth/strategies/strategies.enum.ts @@ -2,6 +2,7 @@ export enum AuthStrategy { // Internal AccessToken = "access_token", RefreshToken = "refresh_token", + ApiToken = "api_token", // Auth Provider Spotify = "spotify", diff --git a/src/database/migrations/07-CreateApiTokenTable.ts b/src/database/migrations/07-CreateApiTokenTable.ts new file mode 100644 index 0000000..4f53b37 --- /dev/null +++ b/src/database/migrations/07-CreateApiTokenTable.ts @@ -0,0 +1,77 @@ +import { + MigrationInterface, + QueryRunner, + Table, + TableIndex, + TableForeignKey, +} from "typeorm"; +import { TableColumnOptions } from "typeorm/schema-builder/options/TableColumnOptions"; + +const primaryUUIDColumn: TableColumnOptions = { + name: "id", + type: "uuid", + isPrimary: true, + isGenerated: true, + generationStrategy: "uuid", +}; + +export class CreateApiTokensTable0000000000007 implements MigrationInterface { + async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "api_token", + columns: [ + primaryUUIDColumn, + { + name: "userId", + type: "uuid", + }, + { + name: "description", + type: "varchar", + }, + { + name: "token", + type: "varchar", + isUnique: true, + }, + { + name: "createdAt", + type: "timestamp", + default: "CURRENT_TIMESTAMP", + }, + { + name: "lastUsedAt", + type: "timestamp", + default: "CURRENT_TIMESTAMP", + }, + { + name: "revokedAt", + type: "timestamp", + default: null, + isNullable: true, + }, + ], + indices: [ + new TableIndex({ + name: "IDX_API_TOKEN_USER_ID", + columnNames: ["userId"], + }), + ], + foreignKeys: [ + new TableForeignKey({ + name: "FK_API_TOKEN_USER_ID", + columnNames: ["userId"], + referencedColumnNames: ["id"], + referencedTableName: "user", + }), + ], + }), + true + ); + } + + async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("api_token"); + } +}