From 474500d00e0f2cb7651ff41df0bfc7831329a364 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Fri, 10 Apr 2026 14:38:05 +0200 Subject: [PATCH 01/18] feat: add tsoa for API type control and documentation --- .gitignore | 3 + package-lock.json | 910 ++++++++++++++++-- package.json | 4 +- src/{service/routes/healthcheck.ts => app.ts} | 14 +- src/service/authentication.ts | 94 ++ src/service/controllers/AuthController.ts | 342 +++++++ src/service/controllers/ConfigController.ts | 46 + src/service/controllers/HealthController.ts | 33 + src/service/controllers/HomeController.ts | 39 + src/service/controllers/PushController.ts | 285 ++++++ src/service/controllers/RepoController.ts | 299 ++++++ src/service/controllers/UserController.ts | 55 ++ src/service/index.ts | 33 +- src/service/{routes/home.ts => models.ts} | 19 +- .../{routes/config.ts => proxyStore.ts} | 34 +- src/service/routes/auth.ts | 282 ------ src/service/routes/index.ts | 40 - src/service/routes/push.ts | 278 ------ src/service/routes/repo.ts | 231 ----- src/service/routes/users.ts | 45 - test/services/routes/auth.test.ts | 120 ++- test/services/routes/config.test.ts | 23 +- test/services/routes/healthCheck.test.ts | 8 +- test/services/routes/users.test.ts | 26 +- test/testLogin.test.ts | 7 +- test/testPush.test.ts | 10 +- test/testRepoApi.test.ts | 7 +- tsconfig.json | 4 +- tsoa.json | 26 + 29 files changed, 2241 insertions(+), 1076 deletions(-) rename src/{service/routes/healthcheck.ts => app.ts} (74%) create mode 100644 src/service/authentication.ts create mode 100644 src/service/controllers/AuthController.ts create mode 100644 src/service/controllers/ConfigController.ts create mode 100644 src/service/controllers/HealthController.ts create mode 100644 src/service/controllers/HomeController.ts create mode 100644 src/service/controllers/PushController.ts create mode 100644 src/service/controllers/RepoController.ts create mode 100644 src/service/controllers/UserController.ts rename src/service/{routes/home.ts => models.ts} (67%) rename src/service/{routes/config.ts => proxyStore.ts} (51%) delete mode 100644 src/service/routes/auth.ts delete mode 100644 src/service/routes/index.ts delete mode 100644 src/service/routes/push.ts delete mode 100644 src/service/routes/repo.ts delete mode 100644 src/service/routes/users.ts create mode 100644 tsoa.json diff --git a/.gitignore b/.gitignore index c6076f1af..047d2e28e 100644 --- a/.gitignore +++ b/.gitignore @@ -275,3 +275,6 @@ website/.docusaurus # Generated from testing /test/fixtures/test-package/package-lock.json + +# tsoa generated files (regenerated by `npm run build-tsoa`) +src/service/generatedRoutes.ts diff --git a/package-lock.json b/package-lock.json index ee592bc12..40d95f600 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "react-html-parser": "^2.0.2", "react-router-dom": "6.30.3", "simple-git": "^3.30.0", + "tsoa": "^7.0.0-alpha.0", "uuid": "^13.0.0", "validator": "^13.15.26", "yargs": "^17.7.2" @@ -2543,6 +2544,322 @@ "dev": true, "license": "MIT" }, + "node_modules/@hapi/accept": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", + "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/ammo": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", + "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/b64": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", + "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bounce": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz", + "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/call": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", + "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/catbox": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", + "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/podium": "^5.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/catbox-memory": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz", + "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/content": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.1.tgz", + "integrity": "sha512-lQ2vOoFMNYxwKVnKf+3Pi3PfoviM4EJYlT9JbrBPfEc0xKMiVDqqXF8UTE1S1oKhHQliWSP5t6zTKNlmaXBGcQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.0" + } + }, + "node_modules/@hapi/cryptiles": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.3.tgz", + "integrity": "sha512-r6VKalpbMHz4ci3gFjFysBmhwCg70RpYZy6OkjEpdXzAYnYFX5XsW7n4YMJvuIYpnMwLxGUjK/cBhA7X3JDvXw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", + "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hapi": { + "version": "21.4.8", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.8.tgz", + "integrity": "sha512-l93IrEG4iQyM+yKdngWmkPtajkJGM81yfinmSFmiaNHG+r1fgsWaewwcE1hhsFnqPrVZpU8Y3PiVJMb6uT+01Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/accept": "^6.0.3", + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.2", + "@hapi/call": "^9.0.1", + "@hapi/catbox": "^12.1.1", + "@hapi/catbox-memory": "^6.0.2", + "@hapi/heavy": "^8.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/mimos": "^7.0.1", + "@hapi/podium": "^5.0.2", + "@hapi/shot": "^6.0.2", + "@hapi/somever": "^4.1.1", + "@hapi/statehood": "^8.2.1", + "@hapi/subtext": "^8.1.2", + "@hapi/teamwork": "^6.0.1", + "@hapi/topo": "^6.0.2", + "@hapi/validate": "^2.0.1" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@hapi/heavy": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", + "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/iron": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", + "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/mimos": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", + "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "mime-db": "^1.52.0" + } + }, + "node_modules/@hapi/nigel": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", + "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/vise": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/pez": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.1.tgz", + "integrity": "sha512-yg2OS1tC0S1sHXvhUtWsfRn6lrKl9jKtRhZ+EI0woOW/gqX5vM2PZ1459ypCvCYDRLJ9nIyueeEH5MJV1ZDqIg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/content": "^6.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/nigel": "^5.0.1" + } + }, + "node_modules/@hapi/podium": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz", + "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/teamwork": "^6.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/shot": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz", + "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/somever": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", + "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.1.tgz", + "integrity": "sha512-xf72TG/QINW26jUu+uL5H+crE1o8GplIgfPWwPZhnAGJzetIVAQEQYvzq+C0aEVHg5/lMMtQ+L9UryuSa5Yjkg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/iron": "^7.0.1", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/subtext": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.2.tgz", + "integrity": "sha512-2x71YJHmFpCjhIhfiNZdKp63nh3xRPp7RrwH7JoO9R4Sd0DRzzRU/VfX2fMmUR7jcoS5qNET1WyGIaqKpMu/ng==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/content": "^6.0.1", + "@hapi/file": "^3.0.0", + "@hapi/hoek": "^11.0.7", + "@hapi/pez": "^6.1.1", + "@hapi/wreck": "^18.1.0" + } + }, + "node_modules/@hapi/teamwork": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz", + "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/vise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", + "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/wreck": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", + "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "dev": true, @@ -4151,6 +4468,339 @@ "dev": true, "license": "MIT" }, + "node_modules/@tsoa/cli": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-7.0.0-alpha.0.tgz", + "integrity": "sha512-fCBWv6F20qrpwFh5X+vaZs38Bh/5cVwKPhvG/uArR+crZgEowyNl76unLCmYdvycES9Y6g/TKFLagG8qojSNuQ==", + "license": "MIT", + "dependencies": { + "@tsoa/runtime": "^7.0.0-alpha.0", + "@types/multer": "^1.4.12", + "fs-extra": "^11.2.0", + "glob": "^10.3.10", + "handlebars": "^4.7.8", + "merge-anything": "^5.1.7", + "minimatch": "^9.0.1", + "ts-deepmerge": "^7.0.2", + "typescript": "^5.7.2", + "validator": "^13.12.0", + "yaml": "^2.6.1", + "yargs": "^17.7.1" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/cli/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tsoa/cli/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@tsoa/cli/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsoa/runtime": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-7.0.0-alpha.0.tgz", + "integrity": "sha512-zlWYz2bLfaN6WtFoIbLBEAyVhKG4IQKJ9QPzeRFFKeAsh5zYN4/ocnd14XyWs4ehY9TdtTnma2drW89aYNSRYw==", + "license": "MIT", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hapi": "^21.3.12", + "@types/koa": "^2.15.0", + "@types/multer": "^1.4.12", + "express": "^4.21.2", + "reflect-metadata": "^0.2.2", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/runtime/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@tsoa/runtime/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@tsoa/runtime/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@tsoa/runtime/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsoa/runtime/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tsoa/runtime/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/activedirectory2": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@types/activedirectory2/-/activedirectory2-1.2.6.tgz", @@ -4200,7 +4850,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.5", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -4209,12 +4858,17 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", + "license": "MIT" + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "dev": true, @@ -4228,6 +4882,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cookies": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", + "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -4270,7 +4936,6 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -4290,7 +4955,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4320,9 +4984,14 @@ "domhandler": "^2.4.0" } }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -4341,6 +5010,37 @@ "@types/node": "*" } }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "license": "MIT", + "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.9", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz", + "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==", + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, "node_modules/@types/ldapjs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-3.0.6.tgz", @@ -4377,7 +5077,6 @@ }, "node_modules/@types/mime": { "version": "1.3.5", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -4387,6 +5086,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.19.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", @@ -4435,12 +5143,10 @@ }, "node_modules/@types/qs": { "version": "6.9.18", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { @@ -4486,7 +5192,6 @@ }, "node_modules/@types/send": { "version": "0.17.4", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -4497,7 +5202,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -5397,6 +6101,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/array-ify": { "version": "1.0.0", "dev": true, @@ -5721,26 +6431,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", @@ -5772,15 +6462,6 @@ "node": ">= 0.10" } }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/bowser": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", @@ -6835,6 +7516,16 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/dezalgo": { "version": "1.0.4", "dev": true, @@ -8534,7 +9225,6 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "dev": true, "license": "ISC" }, "node_modules/graphql": { @@ -8545,6 +9235,27 @@ "iterall": "1.1.3" } }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "dev": true, @@ -8689,17 +9400,23 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-signature": { @@ -9320,6 +10037,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "dev": true, @@ -9713,7 +10442,6 @@ }, "node_modules/jsonfile": { "version": "6.1.0", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -10670,6 +11398,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-anything": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", + "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -10706,7 +11449,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10927,6 +11669,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "dev": true, @@ -12132,13 +12880,15 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -12269,6 +13019,12 @@ "node": ">= 6" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "dev": true, @@ -12933,7 +13689,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -13030,7 +13785,9 @@ "license": "MIT" }, "node_modules/statuses": { - "version": "2.0.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13675,6 +14432,15 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, "node_modules/ts-node": { "version": "10.9.2", "dev": true, @@ -13751,6 +14517,23 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tsoa": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-7.0.0-alpha.0.tgz", + "integrity": "sha512-o5h2DD1IKa2GF728BHYDL2uSX57a44sX8BLFScR4KbD0xlOmgsSDECneJmouDxf9MJdjHHWc+I1S3sGK82B6kQ==", + "license": "MIT", + "dependencies": { + "@tsoa/cli": "^7.0.0-alpha.0", + "@tsoa/runtime": "^7.0.0-alpha.0" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, "node_modules/tsscmp": { "version": "1.0.6", "license": "MIT", @@ -13923,7 +14706,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13965,6 +14747,19 @@ "node": ">=8" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid-safe": { "version": "2.1.5", "license": "MIT", @@ -14032,7 +14827,6 @@ }, "node_modules/universalify": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -14665,7 +15459,6 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/wordwrapjs": { @@ -14799,7 +15592,6 @@ }, "node_modules/yaml": { "version": "2.8.1", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index c10721372..407a4074a 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,9 @@ "server": "ALLOWED_ORIGINS=* tsx index.ts", "start": "concurrently \"npm run server\" \"npm run client\"", "build": "npm run generate-config-types && npm run build-ui && npm run build-ts", - "build-ts": "tsc --project tsconfig.publish.json && node scripts/fix-shebang.js", + "build-ts": "npm run build-tsoa && tsc --project tsconfig.publish.json && node scripts/fix-shebang.js", "build-ui": "vite build", + "build-tsoa": "tsoa spec-and-routes", "check-types": "tsc", "check-types:server": "tsc --project tsconfig.publish.json --noEmit", "test-shuffle": "NODE_ENV=test vitest --run --dir ./test --sequence.shuffle", @@ -131,6 +132,7 @@ "react-html-parser": "^2.0.2", "react-router-dom": "6.30.3", "simple-git": "^3.30.0", + "tsoa": "^7.0.0-alpha.0", "uuid": "^13.0.0", "validator": "^13.15.26", "yargs": "^17.7.2" diff --git a/src/service/routes/healthcheck.ts b/src/app.ts similarity index 74% rename from src/service/routes/healthcheck.ts rename to src/app.ts index bdc79aa2e..6ad0f79c4 100644 --- a/src/service/routes/healthcheck.ts +++ b/src/app.ts @@ -14,14 +14,8 @@ * limitations under the License. */ -import express, { Request, Response } from 'express'; +import express from 'express'; -const router = express.Router(); - -router.get('/', (_req: Request, res: Response) => { - res.send({ - message: 'ok', - }); -}); - -export default router; +// Minimal entry point used by tsoa for controller/spec discovery. +// The actual configured Express app lives in src/service/index.ts. +export const app = express(); diff --git a/src/service/authentication.ts b/src/service/authentication.ts new file mode 100644 index 000000000..bd378f54f --- /dev/null +++ b/src/service/authentication.ts @@ -0,0 +1,94 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Request } from 'express'; +import { getAPIAuthMethods } from '../config'; +import { assignRoles, validateJwt } from './passport/jwtUtils'; +import type { RoleMapping } from '../config/generated/config'; + +/** + * tsoa authentication handler called for every route decorated with @Security. + * + * Supported security names: + * - 'jwt': Bearer-token validation via OIDC JWT or an existing session. + */ +export async function expressAuthentication( + request: Request, + securityName: string, + + _scopes?: string[], +): Promise { + if (securityName === 'jwt') { + // Already authenticated via session (e.g. passport local login) + if (request.isAuthenticated && request.isAuthenticated()) { + return request.user; + } + + const apiAuthMethods = getAPIAuthMethods(); + const jwtAuthMethod = apiAuthMethods.find((m) => m.type.toLowerCase() === 'jwt'); + + if (!jwtAuthMethod || !jwtAuthMethod.enabled) { + // JWT not configured — pass through (other middleware may enforce auth) + return; + } + + const token = request.header('Authorization'); + if (!token) { + throw Object.assign(new Error('No token provided'), { status: 401 }); + } + + if (!jwtAuthMethod.jwtConfig) { + console.log('JWT configuration is missing'); + throw Object.assign(new Error('JWT configuration is missing'), { status: 500 }); + } + + const { clientID, authorityURL, expectedAudience, roleMapping } = jwtAuthMethod.jwtConfig; + + if (!authorityURL) { + console.log('OIDC authority URL is not configured'); + throw Object.assign(new Error('OIDC authority URL is not configured'), { status: 500 }); + } + + if (!clientID) { + console.log('OIDC client ID is not configured'); + throw Object.assign(new Error('OIDC client ID is not configured'), { status: 500 }); + } + + const audience = expectedAudience || clientID; + const tokenParts = token.split(' '); + const accessToken = tokenParts.length === 2 ? tokenParts[1] : tokenParts[0]; + + const { verifiedPayload, error } = await validateJwt( + accessToken, + authorityURL, + audience, + clientID, + ); + + if (error || !verifiedPayload) { + console.log('JWT validation failed'); + throw Object.assign(new Error(error || 'JWT validation failed'), { status: 401 }); + } + + request.user = verifiedPayload; + assignRoles(roleMapping as RoleMapping, verifiedPayload, request.user); + + console.log('JWT validation successful'); + return verifiedPayload; + } + + throw Object.assign(new Error(`Unknown security scheme: ${securityName}`), { status: 401 }); +} diff --git a/src/service/controllers/AuthController.ts b/src/service/controllers/AuthController.ts new file mode 100644 index 000000000..44ff0de38 --- /dev/null +++ b/src/service/controllers/AuthController.ts @@ -0,0 +1,342 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'; +import { + Body, + Controller, + Get, + Middlewares, + Post, + Request, + Res, + Route, + Tags, + TsoaResponse, +} from 'tsoa'; +import { getPassport, authStrategies } from '../passport'; +import { getAuthMethods } from '../../config'; +import * as db from '../../db'; +import * as passportLocal from '../passport/local'; +import * as passportAD from '../passport/activeDirectory'; +import { User } from '../../db/types'; +import { AuthenticationElement } from '../../config/generated/config'; +import { isAdminUser, toPublicUser } from '../routes/utils'; +import { handleErrorAndLog } from '../../utils/errors'; +import { PublicUser } from '../../db/types'; + +const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost', GIT_PROXY_UI_PORT: uiPort = 3000 } = + process.env; + +// login strategies that will work with /login e.g. take username and password +const appropriateLoginStrategies = [passportLocal.type, passportAD.type]; + +// getLoginStrategy fetches the enabled auth methods and identifies if there's an appropriate +// auth method for username and password login. If there isn't it returns null, if there is it +// returns the first. +const getLoginStrategy = () => { + // returns only enabled auth methods + // returns at least one enabled auth method + const enabledAppropriateLoginStrategies = getAuthMethods().filter((am: AuthenticationElement) => + appropriateLoginStrategies.includes(am.type.toLowerCase()), + ); + // for where no login strategies which work for /login are enabled + // just return null + if (enabledAppropriateLoginStrategies.length === 0) { + return null; + } + // return the first enabled auth method + return enabledAppropriateLoginStrategies[0].type.toLowerCase(); +}; + +/** + * Dynamically selects the login passport strategy and runs it as Express middleware. + * Used by `@Middlewares` on the POST /login route. + */ +export function dynamicLoginMiddleware( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, +): void { + const authType = getLoginStrategy(); + if (authType === null) { + res.status(403).send('Username and Password based Login is not enabled at this time').end(); + return; + } + getPassport().authenticate(authType)(req, res, next); +} + +/** + * Handles the OIDC callback: authenticates the user and redirects on success. + * Used by @Middlewares on the GET /openidconnect/callback route. + */ +export function oidcCallbackMiddleware( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, +): void { + getPassport().authenticate( + authStrategies['openidconnect'].type, + (err: unknown, user: Partial, info: unknown) => { + if (err) { + console.error('Authentication error:', err); + return res.status(500).end(); + } + if (!user) { + console.error('No user found:', info); + return res.status(401).end(); + } + req.logIn(user, (err) => { + if (err) { + console.error('Login error:', err); + return res.status(500).end(); + } + console.log('Logged in successfully. User:', user); + return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); + }); + }, + )(req, res, next); +} + +// ---------- Response types ---------- + +interface AuthResources { + login: { action: 'post'; uri: string }; + profile: { action: 'get'; uri: string }; + logout: { action: 'post'; uri: string }; +} + +interface AuthConfigResponse { + usernamePasswordMethod: string | null; + otherMethods: string[]; +} + +interface LoginResponse { + message: 'success'; + user: PublicUser; +} + +interface LogoutResponse { + isAuth: boolean; + user: Express.User | undefined; +} + +interface CreateUserResponse { + message: string; + username: string; +} + +interface GitAccountBody { + username?: string; + id?: string; + gitAccount: string; +} + +interface CreateUserBody { + username: string; + password: string; + email: string; + gitAccount: string; + admin?: boolean; +} + +/** + * Authentication endpoints. + */ +@Route('api/auth') +@Tags('Auth') +export class AuthController extends Controller { + /** + * Returns links to the available authentication resource endpoints. + */ + @Get('/') + public getResources(): AuthResources { + return { + login: { action: 'post', uri: '/api/auth/login' }, + profile: { action: 'get', uri: '/api/auth/profile' }, + logout: { action: 'post', uri: '/api/auth/logout' }, + }; + } + + /** + * Returns the enabled authentication methods available to the UI. + */ + @Get('/config') + public getAuthConfig(): AuthConfigResponse { + const usernamePasswordMethod = getLoginStrategy(); + return { + // enabled username /password auth method + usernamePasswordMethod, + // other enabled auth methods + otherMethods: getAuthMethods() + .map((am) => am.type.toLowerCase()) + .filter((authType) => authType !== usernamePasswordMethod), + }; + } + + // TODO: provide separate auth endpoints for each auth strategy or chain compatibile auth strategies + // TODO: if providing separate auth methods, inform the frontend so it has relevant UI elements and appropriate client-side behavior + /** + * Authenticates the user with a username/password strategy. + * The appropriate passport strategy is selected dynamically based on configuration. + */ + @Post('/login') + @Middlewares(dynamicLoginMiddleware) + public async login(@Request() req: ExpressRequest): Promise { + // dynamicLoginMiddleware has already authenticated the user and set req.user. + // If strategy called next(), we can log in and return the user profile. + const user = req.user as User; + + await new Promise((resolve, reject) => { + req.logIn(user, (err) => (err ? reject(err) : resolve())); + }); + + const currentUser = toPublicUser(user); + console.log( + `service.routes.auth.login: user logged in, username=${currentUser.username} profile=${JSON.stringify(currentUser)}`, + ); + return { message: 'success', user: currentUser }; + } + + /** + * Initiates the OpenID Connect authentication flow (redirects to the OIDC provider). + * @hidden + */ + @Get('/openidconnect') + @Middlewares(getPassport().authenticate(authStrategies['openidconnect'].type)) + public initiateOIDC(): void { + // Passport middleware handles the redirect. This body is unreachable. + } + + /** + * OpenID Connect callback — exchanges the authorization code for a session. + * @hidden + */ + @Get('/openidconnect/callback') + @Middlewares(oidcCallbackMiddleware) + public handleOIDCCallback(): void { + // oidcCallbackMiddleware handles login and redirect. This body is unreachable. + } + + /** + * Logs out the current user and clears the session cookie. + */ + @Post('/logout') + public async logout(@Request() req: ExpressRequest): Promise { + await new Promise((resolve, reject) => { + req.logout((err: unknown) => (err ? reject(err) : resolve())); + }); + req.res?.clearCookie('connect.sid'); + return { isAuth: req.isAuthenticated(), user: req.user }; + } + + /** + * Returns the profile of the currently authenticated user. + */ + @Get('/profile') + public async getProfile( + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + ): Promise { + if (!req.user) { + return unauthorisedResponse(401, { message: 'Not logged in' }); + } + + const userVal = await db.findUser((req.user as User).username); + if (!userVal) { + return notFoundResponse(404, { message: 'User not found' }); + } + + return toPublicUser(userVal); + } + + /** + * Updates the Git account (username) of a user. + * Admins may update any user; non-admins may only update their own account. + */ + @Post('/gitAccount') + public async updateGitAccount( + @Body() body: GitAccountBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, + @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + ): Promise { + if (!req.user) { + return unauthorisedResponse(401, { message: 'Not logged in' }); + } + + try { + let username = + body.username == null || body.username === 'undefined' ? body.id : body.username; + username = username?.split('@')[0]; + + if (!username) { + return validationErrorResponse(400, { + message: 'Missing username. Git account not updated', + }); + } + + const reqUser = await db.findUser((req.user as User).username); + if (username !== reqUser?.username && !reqUser?.admin) { + return forbiddenResponse(403, { + message: 'Must be an admin to update a different account', + }); + } + + const user = await db.findUser(username); + if (!user) { + return notFoundResponse(404, { message: 'User not found' }); + } + + user.gitAccount = body.gitAccount; + await db.updateUser(user); + this.setStatus(200); + } catch (error: unknown) { + const msg = handleErrorAndLog(error, 'Failed to update git account'); + return internalServerErrorResponse(500, { message: msg }); + } + } + + /** + * Creates a new user. Requires admin privileges. + */ + @Post('/create-user') + public async createUser( + @Body() body: CreateUserBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<403, { message: string }>, + @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(403, { message: 'Not authorized to create users' }); + } + + const { username, password, email, gitAccount, admin: isAdmin = false } = body; + + try { + await db.createUser(username, password, email, gitAccount, isAdmin); + this.setStatus(201); + return { message: 'User created successfully', username }; + } catch (error: unknown) { + const msg = handleErrorAndLog(error, 'Failed to create user'); + return internalServerErrorResponse(500, { message: msg }); + } + } +} diff --git a/src/service/controllers/ConfigController.ts b/src/service/controllers/ConfigController.ts new file mode 100644 index 000000000..6b7e6a854 --- /dev/null +++ b/src/service/controllers/ConfigController.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Controller, Get, Route, Tags } from 'tsoa'; +import * as config from '../../config'; +import type { AttestationConfig, UIRouteAuth } from '../../config/generated/config'; + +/** + * Public configuration endpoints consumed by the UI. + */ +@Route('api/v1/config') +@Tags('Config') +export class ConfigController extends Controller { + @Get('/attestation') + public getAttestation(): AttestationConfig { + return config.getAttestationConfig(); + } + + @Get('/urlShortener') + public getUrlShortener(): string | undefined { + return config.getURLShortener(); + } + + @Get('/contactEmail') + public getContactEmail(): string | undefined { + return config.getContactEmail(); + } + + @Get('/uiRouteAuth') + public getUiRouteAuth(): UIRouteAuth { + return config.getUIRouteAuth(); + } +} diff --git a/src/service/controllers/HealthController.ts b/src/service/controllers/HealthController.ts new file mode 100644 index 000000000..76c1238e3 --- /dev/null +++ b/src/service/controllers/HealthController.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Controller, Get, Route, Tags } from 'tsoa'; + +interface HealthResponse { + message: 'ok'; +} + +/** + * Service health check. + */ +@Route('api/v1/healthcheck') +@Tags('Health') +export class HealthController extends Controller { + @Get('/') + public check(): HealthResponse { + return { message: 'ok' }; + } +} diff --git a/src/service/controllers/HomeController.ts b/src/service/controllers/HomeController.ts new file mode 100644 index 000000000..89279109a --- /dev/null +++ b/src/service/controllers/HomeController.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Controller, Get, Route, Tags } from 'tsoa'; + +interface ApiResources { + healthcheck: string; + push: string; + auth: string; +} + +/** + * API home — lists available resource URIs. + */ +@Route('api') +@Tags('Home') +export class HomeController extends Controller { + @Get('/') + public getResources(): ApiResources { + return { + healthcheck: '/api/v1/healthcheck', + push: '/api/v1/push', + auth: '/api/auth', + }; + } +} diff --git a/src/service/controllers/PushController.ts b/src/service/controllers/PushController.ts new file mode 100644 index 000000000..e034b5678 --- /dev/null +++ b/src/service/controllers/PushController.ts @@ -0,0 +1,285 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Body, + Controller, + Get, + Path, + Post, + Request, + Res, + Route, + Security, + Tags, + TsoaResponse, +} from 'tsoa'; +import type { Request as ExpressRequest } from 'express'; +import * as db from '../../db'; +import { PushQuery } from '../../db/types'; +import { AttestationConfig } from '../../config/generated/config'; +import { getAttestationConfig } from '../../config'; +import { Action } from '../../proxy/actions/Action'; +import { AttestationAnswer, Rejection } from '../../proxy/processors/types'; +import { MessageResponse } from '../models'; + +interface RejectBody { + /** The reason for rejecting the push request. */ + reason: string; +} + +/** Attestation answer as sent by the UI (may include display-only fields). */ +interface AttestationAnswerRequest { + label: string; + checked: boolean; +} + +interface AuthoriseBody { + params: { + attestation: AttestationAnswerRequest[]; + }; +} + +/** + * Push request management (JWT-protected). + */ +@Route('api/v1/push') +@Security('jwt') +@Tags('Push') +export class PushController extends Controller { + /** + * Returns push requests, optionally filtered by query parameters. + * Supported filters: any field from PushQuery (error, blocked, allowPush, authorised, canceled, rejected, type). + */ + @Get('/') + public async getPushes(@Request() req: ExpressRequest): Promise { + const query: Partial = { type: 'push' }; + + for (const key in req.query) { + if (!key) continue; + if (key === 'limit' || key === 'skip') continue; + + const rawValue = req.query[key]; + let parsedValue: boolean | undefined; + if (rawValue === 'false') parsedValue = false; + if (rawValue === 'true') parsedValue = true; + query[key] = parsedValue ?? rawValue?.toString(); + } + + return db.getPushes(query); + } + + /** + * Returns a single push request by ID. + */ + @Get('/{id}') + public async getPush( + @Path() id: string, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + ): Promise { + const push = await db.getPush(id); + if (!push) { + return notFoundResponse(404, { message: 'not found' }); + } + return push; + } + + /** + * Rejects a pending push request. + */ + @Post('/{id}/reject') + public async rejectPush( + @Path() id: string, + @Body() body: RejectBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + ): Promise { + if (!req.user) { + return unauthorisedResponse(401, { message: 'Not logged in' }); + } + + const { reason } = body; + if (!reason || !reason.trim()) { + return validationErrorResponse(400, { message: 'Rejection reason is required' }); + } + + const { username } = req.user as { username: string }; + + const push = await db.getPush(id); + if (!push) { + return notFoundResponse(404, { message: 'Push request not found' }); + } + + if (!push.userEmail) { + return validationErrorResponse(400, { message: 'Push request has no user email' }); + } + + const committerEmail = push.userEmail; + const list = await db.getUsers({ email: committerEmail }); + + if (list.length === 0) { + return notFoundResponse(404, { + message: `No user found with the committer's email address: ${committerEmail}`, + }); + } + + if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { + return forbiddenResponse(403, { message: 'Cannot reject your own changes' }); + } + + const isAllowed = await db.canUserApproveRejectPush(id, username); + if (!isAllowed) { + return forbiddenResponse(403, { + message: `User ${username} is not authorised to reject changes on this project`, + }); + } + + const reviewerList = await db.getUsers({ username }); + const reviewerEmail = reviewerList[0].email; + + if (!reviewerEmail) { + return notFoundResponse(404, { + message: `There was no registered email address for the reviewer: ${username}`, + }); + } + + const rejection: Rejection = { + reason, + timestamp: new Date(), + reviewer: { username, email: reviewerEmail }, + }; + + const result = await db.reject(id, rejection); + console.log( + `User ${username} rejected push request for ${id}${reason ? ` with reason: ${reason}` : ''}`, + ); + return result; + } + + /** + * Authorises (approves) a pending push request. + */ + @Post('/{id}/authorise') + public async authorisePush( + @Path() id: string, + @Body() body: AuthoriseBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + ): Promise { + if (!req.user) { + return unauthorisedResponse(401, { message: 'Not logged in' }); + } + + const answers = body.params.attestation; + if (!validateAttestation(answers, getAttestationConfig())) { + return validationErrorResponse(400, { message: 'Attestation is not complete' }); + } + + const { username } = req.user as { username: string }; + + const push = await db.getPush(id); + if (!push) { + return notFoundResponse(404, { message: 'Push request not found' }); + } + + // Get the committer of the push via their email address + const committerEmail = push.userEmail; + const list = await db.getUsers({ email: committerEmail }); + + if (list.length === 0) { + return notFoundResponse(404, { + message: `No user found with the committer's email address: ${committerEmail}`, + }); + } + + if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { + return forbiddenResponse(403, { message: 'Cannot approve your own changes' }); + } + + // If we are not the author, now check that we are allowed to authorise on this + // repo + const isAllowed = await db.canUserApproveRejectPush(id, username); + if (!isAllowed) { + return forbiddenResponse(403, { + message: `User ${username} not authorised to approve pushes on this project`, + }); + } + + const reviewerList = await db.getUsers({ username }); + const reviewerEmail = reviewerList[0].email; + + if (!reviewerEmail) { + return notFoundResponse(404, { + message: `There was no registered email address for the reviewer: ${username}`, + }); + } + + const attestation = { + answers, + timestamp: new Date(), + reviewer: { username, email: reviewerEmail }, + }; + + console.log(`User ${username} approved push request for ${id}`); + return db.authorise(id, attestation); + } + + /** + * Cancels a pending push request. + */ + @Post('/{id}/cancel') + public async cancelPush( + @Path() id: string, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + ): Promise { + if (!req.user) { + return unauthorisedResponse(401, { message: 'Not logged in' }); + } + + const { username } = req.user as { username: string }; + const isAllowed = await db.canUserCancelPush(id, username); + + if (!isAllowed) { + console.log(`User ${username} not authorised to cancel push request for ${id}`); + return forbiddenResponse(403, { + message: `User ${username} not authorised to cancel push requests on this project`, + }); + } + + const result = await db.cancel(id); + console.log(`User ${username} canceled push request for ${id}`); + return result; + } +} + +function validateAttestation(answers: AttestationAnswer[], config: AttestationConfig): boolean { + const configQuestions = config.questions ?? []; + + if (!answers || answers.length !== configQuestions.length) { + return false; + } + + const configLabels = new Set(configQuestions.map((q) => q.label)); + return answers.every((answer) => configLabels.has(answer.label) && !!answer.checked); +} diff --git a/src/service/controllers/RepoController.ts b/src/service/controllers/RepoController.ts new file mode 100644 index 000000000..52a25e3af --- /dev/null +++ b/src/service/controllers/RepoController.ts @@ -0,0 +1,299 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Body, + Controller, + Delete, + Get, + Patch, + Path, + Post, + Request, + Res, + Route, + Security, + Tags, +} from 'tsoa'; +import type { TsoaResponse } from 'tsoa'; +import type { Request as ExpressRequest } from 'express'; +import * as db from '../../db'; +import { Repo, RepoQuery } from '../../db/types'; +import { getProxyURL } from '../urls'; +import { isAdminUser } from '../routes/utils'; +import { getProxy } from '../proxyStore'; +import { handleErrorAndLog } from '../../utils/errors'; +import { MessageResponse } from '../models'; + +interface UsernameBody { + username: string; +} + +interface CreateRepoBody { + url: string; + name: string; + project: string; +} + +interface RepoWithProxy extends Repo { + proxyURL: string; +} + +/** + * Repository management (JWT-protected). + */ +@Route('api/v1/repo') +@Security('jwt') +@Tags('Repositories') +export class RepoController extends Controller { + /** + * Returns repositories, optionally filtered by query parameters. + */ + @Get('/') + public async getRepos(@Request() req: ExpressRequest): Promise { + const proxyURL = getProxyURL(req); + const query: Partial = {}; + + for (const key in req.query) { + if (!key) continue; + if (key === 'limit' || key === 'skip') continue; + + const rawValue = req.query[key]; + let parsedValue: boolean | undefined; + if (rawValue === 'false') parsedValue = false; + if (rawValue === 'true') parsedValue = true; + query[key] = parsedValue ?? rawValue?.toString(); + } + + const repos = await db.getRepos(query); + return repos.map((d) => ({ ...d, proxyURL })); + } + + /** + * Returns a single repository by ID. + */ + @Get('/{id}') + public async getRepo( + @Path() id: string, + @Request() req: ExpressRequest, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + ): Promise { + const proxyURL = getProxyURL(req); + const repo = await db.getRepoById(id); + if (!repo) { + return notFoundResponse(404, { message: `Repository ${id} not found` }); + } + return { ...repo, proxyURL }; + } + + /** + * Creates a new repository. May restart the proxy if a new origin is added. + */ + @Post('/') + public async createRepo( + @Body() body: CreateRepoBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, + @Res() conflictResponse: TsoaResponse<409, { message: string }>, + @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + const repoUrl = body.url; + + if (!repoUrl) { + return validationErrorResponse(400, { message: 'Repository url is required' }); + } + + const existing = await db.getRepoByUrl(repoUrl); + if (existing) { + return conflictResponse(409, { message: `Repository ${repoUrl} already exists!` }); + } + + try { + // figure out if this represent a new domain to proxy + let newOrigin = true; + + const existingHosts = await db.getAllProxiedHosts(); + existingHosts.forEach((h) => { + // assume SSL is in use and that our origins are missing the protocol + if (repoUrl.startsWith(`https://${h}`)) { + newOrigin = false; + } + }); + + console.log( + `API request to proxy repository ${repoUrl} is for a new origin: ${newOrigin},\n\texisting origin list was: ${JSON.stringify(existingHosts)}`, + ); + + const repoDetails = await db.createRepo(body); + const proxyURL = getProxyURL(req); + + // restart the proxy if we're proxying a new domain + if (newOrigin) { + console.log('Restarting the proxy to handle an additional host'); + const proxy = getProxy(); + await proxy.stop(); + await proxy.start(); + } + + return { ...repoDetails, proxyURL, message: 'created' }; + } catch (error: unknown) { + const msg = handleErrorAndLog(error, 'Repository creation failed'); + return internalServerErrorResponse(500, { message: msg }); + } + } + + /** + * Grants a user push permission on a repository. + */ + @Patch('/{id}/user/push') + public async addPushUser( + @Path() id: string, + @Body() body: UsernameBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + const username = body.username.toLowerCase(); + const user = await db.findUser(username); + if (!user) { + return userNotFoundResponse(400, { error: 'User does not exist' }); + } + + await db.addUserCanPush(id, username); + return { message: 'created' }; + } + + /** + * Grants a user authorise permission on a repository. + */ + @Patch('/{id}/user/authorise') + public async addAuthoriseUser( + @Path() id: string, + @Body() body: UsernameBody, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + const user = await db.findUser(body.username); + if (!user) { + return userNotFoundResponse(400, { error: 'User does not exist' }); + } + + await db.addUserCanAuthorise(id, body.username); + return { message: 'created' }; + } + + /** + * Revokes a user's authorise permission on a repository. + */ + @Delete('/{id}/user/authorise/{username}') + public async removeAuthoriseUser( + @Path() id: string, + @Path() username: string, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + const user = await db.findUser(username); + if (!user) { + return userNotFoundResponse(400, { error: 'User does not exist' }); + } + + await db.removeUserCanAuthorise(id, username); + return { message: 'created' }; + } + + /** + * Revokes a user's push permission on a repository. + */ + @Delete('/{id}/user/push/{username}') + public async removePushUser( + @Path() id: string, + @Path() username: string, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + const user = await db.findUser(username); + if (!user) { + return userNotFoundResponse(400, { error: 'User does not exist' }); + } + + await db.removeUserCanPush(id, username); + return { message: 'created' }; + } + + /** + * Deletes a repository. May restart the proxy if a proxied host is removed. + */ + @Delete('/{id}/delete') + public async deleteRepo( + @Path() id: string, + @Request() req: ExpressRequest, + @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + ): Promise { + if (!isAdminUser(req.user)) { + return unauthorisedResponse(401, { + message: 'You are not authorised to perform this action.', + }); + } + + // determine if we need to restart the proxy + const previousHosts = await db.getAllProxiedHosts(); + await db.deleteRepo(id); + const currentHosts = await db.getAllProxiedHosts(); + + if (currentHosts.length < previousHosts.length) { + console.log('Restarting the proxy to remove a host'); + const proxy = getProxy(); + await proxy.stop(); + await proxy.start(); + } + + return { message: 'deleted' }; + } +} diff --git a/src/service/controllers/UserController.ts b/src/service/controllers/UserController.ts new file mode 100644 index 000000000..99b783906 --- /dev/null +++ b/src/service/controllers/UserController.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Controller, Get, Path, Res, Route, Security, Tags, TsoaResponse } from 'tsoa'; +import * as db from '../../db'; +import { PublicUser } from '../../db/types'; +import { toPublicUser } from '../routes/utils'; + +/** + * User listing (JWT-protected). + */ +@Route('api/v1/user') +@Security('jwt') +@Tags('Users') +export class UserController extends Controller { + /** + * Returns all registered users (public fields only). + */ + @Get('/') + public async getUsers(): Promise { + console.log('fetching users'); + const users = await db.getUsers(); + return users.map(toPublicUser); + } + + /** + * Returns a single user by username. + */ + @Get('/{id}') + public async getUser( + @Path() id: string, + @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + ): Promise { + const username = id.toLowerCase(); + console.log(`Retrieving details for user: ${username}`); + const user = await db.findUser(username); + if (!user) { + return notFoundResponse(404, { message: `User ${username} not found` }); + } + return toPublicUser(user); + } +} diff --git a/src/service/index.ts b/src/service/index.ts index b8ee756b8..3b4e9f46f 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -26,8 +26,11 @@ import * as config from '../config'; import * as db from '../db'; import { serverConfig } from '../config/env'; import { Proxy } from '../proxy'; -import routes from './routes'; +import { RegisterRoutes } from './generatedRoutes'; +import { setProxy } from './proxyStore'; import { configure } from './passport'; +import { ValidateError } from 'tsoa'; +import type { Request, Response, NextFunction } from 'express'; const limiter = rateLimit(config.getRateLimit()); @@ -165,7 +168,33 @@ async function createApp(proxy: Proxy): Promise { app.use(passport.session()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); - app.use('/', routes(proxy)); + + // Make the Proxy instance available to controllers before registering routes. + if (proxy) setProxy(proxy); + RegisterRoutes(app); + + // Error handlers — must be registered after routes. + // Handle tsoa validation errors (missing/invalid fields). + app.use((err: unknown, _req: Request, res: Response, next: NextFunction) => { + if (err instanceof ValidateError) { + return res.status(400).json({ + message: 'Validation failed', + details: err.fields, + }); + } + next(err); + }); + // Handle HTTP errors thrown from controllers (objects with a .status property). + app.use((err: unknown, _req: Request, res: Response, next: NextFunction) => { + const httpErr = err as { status?: number; message?: string }; + const status = typeof httpErr.status === 'number' ? httpErr.status : 500; + const message = httpErr.message ?? 'Internal server error'; + if (!res.headersSent) { + return res.status(status).json({ message }); + } + next(err); + }); + app.use('/', express.static(absBuildPath)); app.get('/*path', (_req, res) => { res.sendFile(path.join(`${absBuildPath}/index.html`)); diff --git a/src/service/routes/home.ts b/src/service/models.ts similarity index 67% rename from src/service/routes/home.ts rename to src/service/models.ts index bfab99c46..1e3efbb32 100644 --- a/src/service/routes/home.ts +++ b/src/service/models.ts @@ -14,18 +14,7 @@ * limitations under the License. */ -import express, { Request, Response } from 'express'; - -const router = express.Router(); - -const resource = { - healthcheck: '/api/v1/healthcheck', - push: '/api/v1/push', - auth: '/api/auth', -}; - -router.get('/', (_req: Request, res: Response) => { - res.send(resource); -}); - -export default router; +/** Generic response carrying a human-readable message. */ +export interface MessageResponse { + message: string; +} diff --git a/src/service/routes/config.ts b/src/service/proxyStore.ts similarity index 51% rename from src/service/routes/config.ts rename to src/service/proxyStore.ts index ad61ab422..f84704072 100644 --- a/src/service/routes/config.ts +++ b/src/service/proxyStore.ts @@ -14,25 +14,21 @@ * limitations under the License. */ -import express, { Request, Response } from 'express'; -import * as config from '../../config'; +import { Proxy } from '../proxy'; -const router = express.Router(); +let _proxy: Proxy | null = null; -router.get('/attestation', (_req: Request, res: Response) => { - res.send(config.getAttestationConfig()); -}); - -router.get('/urlShortener', (_req: Request, res: Response) => { - res.send(config.getURLShortener()); -}); - -router.get('/contactEmail', (_req: Request, res: Response) => { - res.send(config.getContactEmail()); -}); - -router.get('/uiRouteAuth', (_req: Request, res: Response) => { - res.send(config.getUIRouteAuth()); -}); +/** + * Store the Proxy instance so controllers can access it without constructor injection. + * Must be called before RegisterRoutes is invoked in src/service/index.ts. + */ +export function setProxy(proxy: Proxy): void { + _proxy = proxy; +} -export default router; +export function getProxy(): Proxy { + if (!_proxy) { + throw new Error('Proxy has not been initialised. Call setProxy() before using getProxy().'); + } + return _proxy; +} diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts deleted file mode 100644 index a03c80480..000000000 --- a/src/service/routes/auth.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express, { Request, Response, NextFunction } from 'express'; -import { getPassport, authStrategies } from '../passport'; -import { getAuthMethods } from '../../config'; - -import * as db from '../../db'; -import * as passportLocal from '../passport/local'; -import * as passportAD from '../passport/activeDirectory'; - -import { User } from '../../db/types'; -import { AuthenticationElement } from '../../config/generated/config'; - -import { isAdminUser, toPublicUser } from './utils'; -import { handleErrorAndLog } from '../../utils/errors'; - -const router = express.Router(); -const passport = getPassport(); - -const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost', GIT_PROXY_UI_PORT: uiPort = 3000 } = - process.env; - -router.get('/', (_req: Request, res: Response) => { - res.status(200).json({ - login: { - action: 'post', - uri: '/api/auth/login', - }, - profile: { - action: 'get', - uri: '/api/auth/profile', - }, - logout: { - action: 'post', - uri: '/api/auth/logout', - }, - }); -}); - -// login strategies that will work with /login e.g. take username and password -const appropriateLoginStrategies = [passportLocal.type, passportAD.type]; -// getLoginStrategy fetches the enabled auth methods and identifies if there's an appropriate -// auth method for username and password login. If there isn't it returns null, if there is it -// returns the first. -const getLoginStrategy = () => { - // returns only enabled auth methods - // returns at least one enabled auth method - const enabledAppropriateLoginStrategies = getAuthMethods().filter((am: AuthenticationElement) => - appropriateLoginStrategies.includes(am.type.toLowerCase()), - ); - // for where no login strategies which work for /login are enabled - // just return null - if (enabledAppropriateLoginStrategies.length === 0) { - return null; - } - // return the first enabled auth method - return enabledAppropriateLoginStrategies[0].type.toLowerCase(); -}; - -const loginSuccessHandler = () => async (req: Request, res: Response) => { - try { - const currentUser = toPublicUser({ ...req.user } as User); - console.log( - `serivce.routes.auth.login: user logged in, username=${ - currentUser.username - } profile=${JSON.stringify(currentUser)}`, - ); - res.send({ - message: 'success', - user: currentUser, - }); - } catch (error: unknown) { - const msg = handleErrorAndLog(error, 'Error logging user in'); - res.status(500).send(`Failed to login: ${msg}`).end(); - } -}; - -router.get('/config', (req, res) => { - const usernamePasswordMethod = getLoginStrategy(); - res.send({ - // enabled username /password auth method - usernamePasswordMethod: usernamePasswordMethod, - // other enabled auth methods - otherMethods: getAuthMethods() - .map((am) => am.type.toLowerCase()) - .filter((authType) => authType !== usernamePasswordMethod), - }); -}); - -// TODO: provide separate auth endpoints for each auth strategy or chain compatibile auth strategies -// TODO: if providing separate auth methods, inform the frontend so it has relevant UI elements and appropriate client-side behavior -router.post( - '/login', - (req: Request, res: Response, next: NextFunction) => { - const authType = getLoginStrategy(); - if (authType === null) { - res.status(403).send('Username and Password based Login is not enabled at this time').end(); - return; - } - console.log('going to auth with', authType); - return passport.authenticate(authType)(req, res, next); - }, - loginSuccessHandler(), -); - -router.get('/openidconnect', passport.authenticate(authStrategies['openidconnect'].type)); - -router.get('/openidconnect/callback', (req: Request, res: Response, next: NextFunction) => { - passport.authenticate( - authStrategies['openidconnect'].type, - (err: unknown, user: Partial, info: unknown) => { - if (err) { - console.error('Authentication error:', err); - return res.status(500).end(); - } - if (!user) { - console.error('No user found:', info); - return res.status(401).end(); - } - req.logIn(user, (err) => { - if (err) { - console.error('Login error:', err); - return res.status(500).end(); - } - console.log('Logged in successfully. User:', user); - return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); - }); - }, - )(req, res, next); -}); - -router.post('/logout', (req: Request, res: Response, next: NextFunction) => { - req.logout((err: unknown) => { - if (err) return next(err); - }); - res.clearCookie('connect.sid'); - res.send({ isAuth: req.isAuthenticated(), user: req.user }); -}); - -router.get('/profile', async (req: Request, res: Response) => { - if (!req.user) { - res - .status(401) - .send({ - message: 'Not logged in', - }) - .end(); - return; - } - - const userVal = await db.findUser((req.user as User).username); - if (!userVal) { - res.status(404).send({ message: 'User not found' }).end(); - return; - } - - res.send(toPublicUser(userVal)); -}); - -router.post('/gitAccount', async (req: Request, res: Response) => { - if (!req.user) { - res - .status(401) - .send({ - message: 'Not logged in', - }) - .end(); - return; - } - - try { - let username = - req.body.username == null || req.body.username === 'undefined' - ? req.body.id - : req.body.username; - username = username?.split('@')[0]; - - if (!username) { - res - .status(400) - .send({ - message: 'Missing username. Git account not updated', - }) - .end(); - return; - } - - const reqUser = await db.findUser((req.user as User).username); - if (username !== reqUser?.username && !reqUser?.admin) { - res - .status(403) - .send({ - message: 'Must be an admin to update a different account', - }) - .end(); - return; - } - - const user = await db.findUser(username); - if (!user) { - res - .status(404) - .send({ - message: 'User not found', - }) - .end(); - return; - } - - user.gitAccount = req.body.gitAccount; - db.updateUser(user); - res.status(200).end(); - } catch (error: unknown) { - const msg = handleErrorAndLog(error, 'Failed to update git account'); - res - .status(500) - .send({ - message: msg, - }) - .end(); - } -}); - -router.post('/create-user', async (req: Request, res: Response) => { - if (!isAdminUser(req.user)) { - res - .status(403) - .send({ - message: 'Not authorized to create users', - }) - .end(); - return; - } - - try { - const { username, password, email, gitAccount, admin: isAdmin = false } = req.body; - - if (!username || !password || !email || !gitAccount) { - res - .status(400) - .send({ - message: - 'Missing required fields: username, password, email, and gitAccount are required', - }) - .end(); - return; - } - - await db.createUser(username, password, email, gitAccount, isAdmin); - res - .status(201) - .send({ - message: 'User created successfully', - username, - }) - .end(); - } catch (error: unknown) { - const msg = handleErrorAndLog(error, 'Failed to create user'); - res - .status(500) - .send({ - message: msg, - }) - .end(); - } -}); - -export default { router, loginSuccessHandler }; diff --git a/src/service/routes/index.ts b/src/service/routes/index.ts deleted file mode 100644 index 80d6c315d..000000000 --- a/src/service/routes/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import auth from './auth'; -import push from './push'; -import home from './home'; -import repo from './repo'; -import users from './users'; -import healthcheck from './healthcheck'; -import config from './config'; -import { jwtAuthHandler } from '../passport/jwtAuthHandler'; -import { Proxy } from '../../proxy'; - -const routes = (proxy: Proxy) => { - const router = express.Router(); - router.use('/api', home); - router.use('/api/auth', auth.router); - router.use('/api/v1/healthcheck', healthcheck); - router.use('/api/v1/push', jwtAuthHandler(), push); - router.use('/api/v1/repo', jwtAuthHandler(), repo(proxy)); - router.use('/api/v1/user', jwtAuthHandler(), users); - router.use('/api/v1/config', config); - return router; -}; - -export default routes; diff --git a/src/service/routes/push.ts b/src/service/routes/push.ts deleted file mode 100644 index 1e900d6ea..000000000 --- a/src/service/routes/push.ts +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express, { Request, Response } from 'express'; -import * as db from '../../db'; -import { PushQuery } from '../../db/types'; -import { AttestationConfig } from '../../config/generated/config'; -import { getAttestationConfig } from '../../config'; -import { AttestationAnswer, Rejection } from '../../proxy/processors/types'; - -interface AuthoriseRequest { - params: { - attestation: AttestationAnswer[]; - }; -} - -const router = express.Router(); - -router.get('/', async (req: Request, res: Response) => { - const query: Partial = { - type: 'push', - }; - - for (const key in req.query) { - if (!key) continue; - if (key === 'limit' || key === 'skip') continue; - - const rawValue = req.query[key]; - let parsedValue: boolean | undefined; - if (rawValue === 'false') parsedValue = false; - if (rawValue === 'true') parsedValue = true; - query[key] = parsedValue ?? rawValue?.toString(); - } - - res.send(await db.getPushes(query)); -}); - -router.get('/:id', async (req: Request<{ id: string }>, res: Response) => { - const id = req.params.id; - const push = await db.getPush(id); - if (push) { - res.send(push); - } else { - res.status(404).send({ - message: 'not found', - }); - } -}); - -router.post('/:id/reject', async (req: Request<{ id: string }>, res: Response) => { - if (!req.user) { - res.status(401).send({ - message: 'Not logged in', - }); - return; - } - - const id = req.params.id; - const { username } = req.user as { username: string }; - const { reason } = req.body; - - if (!reason || !reason.trim()) { - res.status(400).send({ - message: 'Rejection reason is required', - }); - return; - } - - // Get the push request - const push = await getValidPushOrRespond(id, res); - if (!push) return; - - // Get the committer of the push via their email - const committerEmail = push.userEmail; - const list = await db.getUsers({ email: committerEmail }); - - if (list.length === 0) { - res.status(404).send({ - message: `No user found with the committer's email address: ${committerEmail}`, - }); - return; - } - - if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { - res.status(403).send({ - message: `Cannot reject your own changes`, - }); - return; - } - - const isAllowed = await db.canUserApproveRejectPush(id, username); - - if (isAllowed) { - const reviewerList = await db.getUsers({ username }); - const reviewerEmail = reviewerList[0].email; - - if (!reviewerEmail) { - res.status(404).send({ - message: `There was no registered email address for the reviewer: ${username}`, - }); - return; - } - - const rejection: Rejection = { - reason, - timestamp: new Date(), - reviewer: { - username, - email: reviewerEmail, - }, - }; - - const result = await db.reject(id, rejection); - console.log( - `User ${username} rejected push request for ${id}${reason ? ` with reason: ${reason}` : ''}`, - ); - res.send(result); - } else { - res.status(403).send({ - message: `User ${username} is not authorised to reject changes on this project`, - }); - } -}); - -router.post( - '/:id/authorise', - async (req: Request<{ id: string }, unknown, AuthoriseRequest>, res: Response) => { - if (!req.user) { - res.status(401).send({ - message: 'Not logged in', - }); - return; - } - - const answers = req.body.params?.attestation; - - const attestationComplete = validateAttestation(answers, getAttestationConfig()); - - if (!attestationComplete) { - res.status(400).send({ - message: 'Attestation is not complete', - }); - return; - } - - const id = req.params.id; - - const { username } = req.user as { username: string }; - - const push = await db.getPush(id); - if (!push) { - res.status(404).send({ - message: 'Push request not found', - }); - return; - } - - // Get the committer of the push via their email address - const committerEmail = push.userEmail; - - const list = await db.getUsers({ email: committerEmail }); - - if (list.length === 0) { - res.status(404).send({ - message: `No user found with the committer's email address: ${committerEmail}`, - }); - return; - } - - if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { - res.status(403).send({ - message: `Cannot approve your own changes`, - }); - return; - } - - // If we are not the author, now check that we are allowed to authorise on this - // repo - const isAllowed = await db.canUserApproveRejectPush(id, username); - if (isAllowed) { - console.log(`User ${username} approved push request for ${id}`); - - const reviewerList = await db.getUsers({ username }); - const reviewerEmail = reviewerList[0].email; - - if (!reviewerEmail) { - res.status(404).send({ - message: `There was no registered email address for the reviewer: ${username}`, - }); - return; - } - - const attestation = { - answers, - timestamp: new Date(), - reviewer: { - username, - email: reviewerEmail, - }, - }; - const result = await db.authorise(id, attestation); - res.send(result); - } else { - res.status(403).send({ - message: `User ${username} not authorised to approve pushes on this project`, - }); - } - }, -); - -router.post('/:id/cancel', async (req: Request<{ id: string }>, res: Response) => { - if (!req.user) { - res.status(401).send({ - message: 'Not logged in', - }); - return; - } - - const id = req.params.id; - const { username } = req.user as { username: string }; - - const isAllowed = await db.canUserCancelPush(id, username); - - if (isAllowed) { - const result = await db.cancel(id); - console.log(`User ${username} canceled push request for ${id}`); - res.send(result); - } else { - console.log(`User ${username} not authorised to cancel push request for ${id}`); - res.status(403).send({ - message: `User ${username} not authorised to cancel push requests on this project`, - }); - } -}); - -async function getValidPushOrRespond(id: string, res: Response) { - console.log('getValidPushOrRespond', { id }); - const push = await db.getPush(id); - - if (!push) { - res.status(404).send({ message: `Push request not found` }); - return null; - } - - if (!push.userEmail) { - res.status(400).send({ message: `Push request has no user email` }); - return null; - } - - return push; -} - -function validateAttestation(answers: AttestationAnswer[], config: AttestationConfig): boolean { - const configQuestions = config.questions ?? []; - - if (answers.length !== configQuestions.length) { - return false; - } - - const configLabels = new Set(configQuestions.map((q) => q.label)); - - return answers.every((answer) => configLabels.has(answer.label) && !!answer.checked); -} - -export default router; diff --git a/src/service/routes/repo.ts b/src/service/routes/repo.ts deleted file mode 100644 index 7e259d0aa..000000000 --- a/src/service/routes/repo.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express, { Request, Response } from 'express'; - -import * as db from '../../db'; -import { getProxyURL } from '../urls'; -import { getAllProxiedHosts } from '../../db'; -import { RepoQuery } from '../../db/types'; -import { isAdminUser } from './utils'; -import { Proxy } from '../../proxy'; -import { handleErrorAndLog } from '../../utils/errors'; - -function repo(proxy: Proxy) { - const router = express.Router(); - - router.get('/', async (req: Request, res: Response) => { - const proxyURL = getProxyURL(req); - const query: Partial = {}; - - for (const key in req.query) { - if (!key) continue; - if (key === 'limit' || key === 'skip') continue; - - const rawValue = req.query[key]; - let parsedValue: boolean | undefined; - if (rawValue === 'false') parsedValue = false; - if (rawValue === 'true') parsedValue = true; - query[key] = parsedValue ?? rawValue?.toString(); - } - - const qd = await db.getRepos(query); - res.send(qd.map((d) => ({ ...d, proxyURL }))); - }); - - router.get('/:id', async (req: Request<{ id: string }>, res: Response) => { - const proxyURL = getProxyURL(req); - const _id = req.params.id; - const qd = await db.getRepoById(_id); - res.send({ ...qd, proxyURL }); - }); - - router.patch('/:id/user/push', async (req: Request<{ id: string }>, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - const _id = req.params.id; - const username = req.body.username.toLowerCase(); - const user = await db.findUser(username); - - if (!user) { - res.status(400).send({ error: 'User does not exist' }); - return; - } - - await db.addUserCanPush(_id, username); - res.send({ message: 'created' }); - }); - - router.patch('/:id/user/authorise', async (req: Request<{ id: string }>, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - const _id = req.params.id; - const username = req.body.username; - const user = await db.findUser(username); - - if (!user) { - res.status(400).send({ error: 'User does not exist' }); - return; - } - - await db.addUserCanAuthorise(_id, username); - res.send({ message: 'created' }); - }); - - router.delete( - '/:id/user/authorise/:username', - async (req: Request<{ id: string; username: string }>, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - const _id = req.params.id; - const username = req.params.username; - const user = await db.findUser(username); - - if (!user) { - res.status(400).send({ error: 'User does not exist' }); - return; - } - - await db.removeUserCanAuthorise(_id, username); - res.send({ message: 'created' }); - }, - ); - - router.delete( - '/:id/user/push/:username', - async (req: Request<{ id: string; username: string }>, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - const _id = req.params.id; - const username = req.params.username; - const user = await db.findUser(username); - - if (!user) { - res.status(400).send({ error: 'User does not exist' }); - return; - } - - await db.removeUserCanPush(_id, username); - res.send({ message: 'created' }); - }, - ); - - router.delete('/:id/delete', async (req: Request<{ id: string }>, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - const _id = req.params.id; - - // determine if we need to restart the proxy - const previousHosts = await getAllProxiedHosts(); - await db.deleteRepo(_id); - const currentHosts = await getAllProxiedHosts(); - - if (currentHosts.length < previousHosts.length) { - // restart the proxy - console.log('Restarting the proxy to remove a host'); - await proxy.stop(); - await proxy.start(); - } - - res.send({ message: 'deleted' }); - }); - - router.post('/', async (req: Request, res: Response) => { - if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorised to perform this action.', - }); - return; - } - - if (!req.body.url) { - res.status(400).send({ - message: 'Repository url is required', - }); - return; - } - - const repo = await db.getRepoByUrl(req.body.url); - if (repo) { - res.status(409).send({ - message: `Repository ${req.body.url} already exists!`, - }); - } else { - try { - // figure out if this represent a new domain to proxy - let newOrigin = true; - - const existingHosts = await getAllProxiedHosts(); - existingHosts.forEach((h) => { - // assume SSL is in use and that our origins are missing the protocol - if (req.body.url.startsWith(`https://${h}`)) { - newOrigin = false; - } - }); - - console.log( - `API request to proxy repository ${req.body.url} is for a new origin: ${newOrigin},\n\texisting origin list was: ${JSON.stringify(existingHosts)}`, - ); - - // create the repository - const repoDetails = await db.createRepo(req.body); - const proxyURL = getProxyURL(req); - - // restart the proxy if we're proxying a new domain - if (newOrigin) { - console.log('Restarting the proxy to handle an additional host'); - await proxy.stop(); - await proxy.start(); - } - - // return data on the new repository (including it's _id and the proxyUrl) - res.send({ ...repoDetails, proxyURL, message: 'created' }); - } catch (error: unknown) { - const msg = handleErrorAndLog(error, 'Repository creation failed'); - res.status(500).send({ message: msg }); - } - } - }); - - return router; -} - -export default repo; diff --git a/src/service/routes/users.ts b/src/service/routes/users.ts deleted file mode 100644 index 7a20307e9..000000000 --- a/src/service/routes/users.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express, { Request, Response } from 'express'; -const router = express.Router(); - -import * as db from '../../db'; -import { toPublicUser } from './utils'; - -router.get('/', async (req: Request, res: Response) => { - console.log('fetching users'); - const users = await db.getUsers(); - res.send(users.map(toPublicUser)); -}); - -router.get('/:id', async (req: Request<{ id: string }>, res: Response) => { - const username = req.params.id.toLowerCase(); - console.log(`Retrieving details for user: ${username}`); - const user = await db.findUser(username); - if (!user) { - res - .status(404) - .send({ - message: `User ${username} not found`, - }) - .end(); - return; - } - res.send(toPublicUser(user)); -}); - -export default router; diff --git a/test/services/routes/auth.test.ts b/test/services/routes/auth.test.ts index 4008cf9ed..3296761d6 100644 --- a/test/services/routes/auth.test.ts +++ b/test/services/routes/auth.test.ts @@ -16,22 +16,53 @@ import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest'; import request from 'supertest'; -import express, { Express, Request, Response } from 'express'; -import authRoutes from '../../../src/service/routes/auth'; +import express, { Express } from 'express'; +import { RegisterRoutes } from '../../../src/service/generatedRoutes'; import * as db from '../../../src/db'; +import { ValidateError } from 'tsoa'; -const newApp = (username?: string): Express => { +/** + * Builds a minimal Express app with tsoa routes registered. + * When `username` is supplied the request is pre-authenticated via middleware, + * simulating a session-authenticated user. + */ +const newApp = (username?: string, isAdmin = false): Express => { const app = express(); app.use(express.json()); if (username) { app.use((req, _res, next) => { - req.user = { username }; + req.user = { username, admin: isAdmin }; + (req as any).isAuthenticated = () => true; + next(); + }); + } else { + app.use((_req, _res, next) => { + (_req as any).isAuthenticated = () => false; next(); }); } - app.use('/auth', authRoutes.router); + // Generic error handler so tsoa thrown errors propagate as HTTP responses. + RegisterRoutes(app); + + // tsoa validation errors + app.use((err: any, _req: any, res: any, next: any) => { + if (err instanceof ValidateError) { + return res.status(400).json({ + message: 'Validation failed', + details: err.fields, + }); + } + next(err); + }); + + // Generic error handler so tsoa thrown errors propagate as HTTP responses. + app.use((err: any, _req: any, res: any, next: any) => { + if (res.headersSent) return next(err); + res.status(err.status ?? 500).json({ message: err.message }); + }); + return app; }; @@ -40,7 +71,7 @@ describe('Auth API', () => { vi.restoreAllMocks(); }); - describe('POST /gitAccount', () => { + describe('POST /api/auth/gitAccount', () => { beforeEach(() => { vi.spyOn(db, 'findUser').mockImplementation((username: string) => { if (username === 'alice') { @@ -73,7 +104,7 @@ describe('Auth API', () => { }); it('should return 401 Unauthorized if authenticated user not in request', async () => { - const res = await request(newApp()).post('/auth/gitAccount').send({ + const res = await request(newApp()).post('/api/auth/gitAccount').send({ username: 'alice', gitAccount: '', }); @@ -82,7 +113,7 @@ describe('Auth API', () => { }); it('should return 400 Bad Request if username is missing', async () => { - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -90,7 +121,7 @@ describe('Auth API', () => { }); it('should return 400 Bad Request if username is undefined', async () => { - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: undefined, gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -99,7 +130,7 @@ describe('Auth API', () => { }); it('should return 400 Bad Request if username is null', async () => { - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: null, gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -108,7 +139,7 @@ describe('Auth API', () => { }); it('should return 400 Bad Request if username is an empty string', async () => { - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: '', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -117,7 +148,7 @@ describe('Auth API', () => { }); it('should return 403 Forbidden if user is not an admin', async () => { - const res = await request(newApp('bob')).post('/auth/gitAccount').send({ + const res = await request(newApp('bob')).post('/api/auth/gitAccount').send({ username: 'alice', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -126,7 +157,7 @@ describe('Auth API', () => { }); it('should return 404 Not Found if user is not found', async () => { - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: 'non-existent-user', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -137,7 +168,7 @@ describe('Auth API', () => { it('should return 200 OK if user is an admin and updates git account for authenticated user', async () => { const updateUserSpy = vi.spyOn(db, 'updateUser').mockResolvedValue(); - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: 'alice', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -158,7 +189,7 @@ describe('Auth API', () => { it("should prevent non-admin users from changing a different user's gitAccount", async () => { const updateUserSpy = vi.spyOn(db, 'updateUser').mockResolvedValue(); - const res = await request(newApp('bob')).post('/auth/gitAccount').send({ + const res = await request(newApp('bob')).post('/api/auth/gitAccount').send({ username: 'phil', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -170,7 +201,7 @@ describe('Auth API', () => { it("should allow admin users to change a different user's gitAccount", async () => { const updateUserSpy = vi.spyOn(db, 'updateUser').mockResolvedValue(); - const res = await request(newApp('alice')).post('/auth/gitAccount').send({ + const res = await request(newApp('alice', true)).post('/api/auth/gitAccount').send({ username: 'bob', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -191,7 +222,7 @@ describe('Auth API', () => { it('should allow non-admin users to update their own gitAccount', async () => { const updateUserSpy = vi.spyOn(db, 'updateUser').mockResolvedValue(); - const res = await request(newApp('bob')).post('/auth/gitAccount').send({ + const res = await request(newApp('bob')).post('/api/auth/gitAccount').send({ username: 'bob', gitAccount: 'UPDATED_GIT_ACCOUNT', }); @@ -210,46 +241,9 @@ describe('Auth API', () => { }); }); - describe('loginSuccessHandler', () => { - it('should log in user and return public user data', async () => { - const user = { - username: 'bob', - password: 'secret', - email: 'bob@example.com', - displayName: 'Bob', - admin: false, - gitAccount: '', - title: '', - }; - - const sendSpy = vi.fn(); - const res = { - send: sendSpy, - }; - - await authRoutes.loginSuccessHandler()( - { user } as unknown as Request, - res as unknown as Response, - ); - - expect(sendSpy).toHaveBeenCalledOnce(); - expect(sendSpy).toHaveBeenCalledWith({ - message: 'success', - user: { - admin: false, - displayName: 'Bob', - email: 'bob@example.com', - gitAccount: '', - title: '', - username: 'bob', - }, - }); - }); - }); - - describe('GET /profile', () => { + describe('GET /api/auth/profile', () => { it('should return 401 Unauthorized if user is not logged in', async () => { - const res = await request(newApp()).get('/auth/profile'); + const res = await request(newApp()).get('/api/auth/profile'); expect(res.status).toBe(401); }); @@ -265,7 +259,7 @@ describe('Auth API', () => { title: '', }); - const res = await request(newApp('alice')).get('/auth/profile'); + const res = await request(newApp('alice')).get('/api/auth/profile'); expect(res.status).toBe(200); expect(res.body).toEqual({ username: 'alice', @@ -278,15 +272,17 @@ describe('Auth API', () => { }); it('should return 404 Not Found if user is not found', async () => { - const res = await request(newApp('non-existent-user')).get('/auth/profile'); + vi.spyOn(db, 'findUser').mockResolvedValue(null); + + const res = await request(newApp('non-existent-user')).get('/api/auth/profile'); expect(res.status).toBe(404); expect(res.body).toEqual({ message: 'User not found' }); }); }); - describe('GET /', () => { + describe('GET /api/auth', () => { it('should return 200 OK and the auth endpoints', async () => { - const res = await request(newApp()).get('/auth'); + const res = await request(newApp()).get('/api/auth'); expect(res.status).toBe(200); expect(res.body).toEqual({ login: { @@ -305,9 +301,9 @@ describe('Auth API', () => { }); }); - describe('GET /config', () => { + describe('GET /api/auth/config', () => { it('should return 200 OK and the default auth config', async () => { - const res = await request(newApp()).get('/auth/config'); + const res = await request(newApp()).get('/api/auth/config'); expect(res.status).toBe(200); expect(res.body).toEqual({ usernamePasswordMethod: 'local', diff --git a/test/services/routes/config.test.ts b/test/services/routes/config.test.ts index 4caa53758..536e3d7ce 100644 --- a/test/services/routes/config.test.ts +++ b/test/services/routes/config.test.ts @@ -17,7 +17,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import express, { Express } from 'express'; import request from 'supertest'; -import configRouter from '../../../src/service/routes/config'; +import { RegisterRoutes } from '../../../src/service/generatedRoutes'; import * as config from '../../../src/config'; describe('Config API', () => { @@ -26,7 +26,7 @@ describe('Config API', () => { beforeEach(() => { app = express(); app.use(express.json()); - app.use('/config', configRouter); + RegisterRoutes(app); vi.spyOn(config, 'getAttestationConfig').mockReturnValue({ questions: [] }); vi.spyOn(config, 'getURLShortener').mockReturnValue('https://url-shortener.com'); @@ -38,26 +38,27 @@ describe('Config API', () => { vi.restoreAllMocks(); }); - it('GET /config/attestation should return 200 OK and the default attestation config', async () => { - const res = await request(app).get('/config/attestation'); + it('GET /api/v1/config/attestation should return 200 OK and the default attestation config', async () => { + const res = await request(app).get('/api/v1/config/attestation'); expect(res.status).toBe(200); expect(res.body).toEqual({ questions: [] }); }); - it('GET /config/urlShortener should return 200 OK and the default url shortener config', async () => { - const res = await request(app).get('/config/urlShortener'); + it('GET /api/v1/config/urlShortener should return 200 OK and the url shortener config', async () => { + const res = await request(app).get('/api/v1/config/urlShortener'); expect(res.status).toBe(200); - expect(res.text).toBe('https://url-shortener.com'); // Check res.text as it gets serialized as a string + // tsoa sends plain strings via res.send() so the value is in res.text, not res.body. + expect(res.text).toBe('https://url-shortener.com'); }); - it('GET /config/contactEmail should return 200 OK and the default contact email', async () => { - const res = await request(app).get('/config/contactEmail'); + it('GET /api/v1/config/contactEmail should return 200 OK and the contact email', async () => { + const res = await request(app).get('/api/v1/config/contactEmail'); expect(res.status).toBe(200); expect(res.text).toBe('test@example.com'); }); - it('GET /config/uiRouteAuth should return 200 OK and the default ui route auth config', async () => { - const res = await request(app).get('/config/uiRouteAuth'); + it('GET /api/v1/config/uiRouteAuth should return 200 OK and the ui route auth config', async () => { + const res = await request(app).get('/api/v1/config/uiRouteAuth'); expect(res.status).toBe(200); expect(res.body).toEqual({ enabled: false, rules: [] }); }); diff --git a/test/services/routes/healthCheck.test.ts b/test/services/routes/healthCheck.test.ts index 122203210..c1e6c02c6 100644 --- a/test/services/routes/healthCheck.test.ts +++ b/test/services/routes/healthCheck.test.ts @@ -17,7 +17,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import express, { Express } from 'express'; import request from 'supertest'; -import healthcheck from '../../../src/service/routes/healthcheck'; +import { RegisterRoutes } from '../../../src/service/generatedRoutes'; describe('Health Check API', () => { let app: Express; @@ -25,11 +25,11 @@ describe('Health Check API', () => { beforeEach(() => { app = express(); app.use(express.json()); - app.use('/healthCheck', healthcheck); + RegisterRoutes(app); }); - it('GET /healthCheck should return 200 OK and the health check message', async () => { - const res = await request(app).get('/healthCheck'); + it('GET /api/v1/healthcheck should return 200 OK and the health check message', async () => { + const res = await request(app).get('/api/v1/healthcheck'); expect(res.status).toBe(200); expect(res.body).toEqual({ message: 'ok' }); }); diff --git a/test/services/routes/users.test.ts b/test/services/routes/users.test.ts index 41ec52f58..9ec661e26 100644 --- a/test/services/routes/users.test.ts +++ b/test/services/routes/users.test.ts @@ -17,7 +17,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import express, { Express } from 'express'; import request from 'supertest'; -import usersRouter from '../../../src/service/routes/users'; +import { RegisterRoutes } from '../../../src/service/generatedRoutes'; import * as db from '../../../src/db'; describe('Users API', () => { @@ -26,7 +26,17 @@ describe('Users API', () => { beforeEach(() => { app = express(); app.use(express.json()); - app.use('/users', usersRouter); + // Pre-authenticate so JWT security allows through in tests. + app.use((req, _res, next) => { + req.user = { username: 'testuser' }; + (req as any).isAuthenticated = () => true; + next(); + }); + RegisterRoutes(app); + app.use((err: any, _req: any, res: any, next: any) => { + if (res.headersSent) return next(err); + res.status(err.status ?? 500).json({ message: err.message }); + }); vi.spyOn(db, 'getUsers').mockResolvedValue([ { @@ -53,8 +63,8 @@ describe('Users API', () => { vi.restoreAllMocks(); }); - it('GET /users only serializes public data needed for ui, not user secrets like password', async () => { - const res = await request(app).get('/users'); + it('GET /api/v1/user only serializes public data needed for ui, not user secrets like password', async () => { + const res = await request(app).get('/api/v1/user'); expect(res.status).toBe(200); expect(res.body).toEqual([ @@ -69,8 +79,8 @@ describe('Users API', () => { ]); }); - it('GET /users/:id does not serialize password', async () => { - const res = await request(app).get('/users/bob'); + it('GET /api/v1/user/:id does not serialize password', async () => { + const res = await request(app).get('/api/v1/user/bob'); expect(res.status).toBe(200); console.log(`Response body: ${JSON.stringify(res.body)}`); @@ -84,10 +94,10 @@ describe('Users API', () => { }); }); - it('GET /users/:id should return 404 Not Found if user is not found', async () => { + it('GET /api/v1/user/:id should return 404 Not Found if user is not found', async () => { vi.restoreAllMocks(); - const res = await request(app).get('/users/non-existent'); + const res = await request(app).get('/api/v1/user/non-existent'); expect(res.status).toBe(404); expect(res.body).toEqual({ message: 'User non-existent not found' }); }); diff --git a/test/testLogin.test.ts b/test/testLogin.test.ts index 07ec1d2ba..472a253aa 100644 --- a/test/testLogin.test.ts +++ b/test/testLogin.test.ts @@ -197,9 +197,10 @@ describe('login', () => { }); expect(res.status).toBe(400); - expect(res.body.message).toBe( - 'Missing required fields: username, password, email, and gitAccount are required', - ); + // tsoa validates required body fields before the controller runs. + // The 'password' field is missing; tsoa prefixes body field errors with 'body.'. + expect(res.body.message).toBe('Validation failed'); + expect(res.body.details).toHaveProperty('body.password'); }); it('should successfully create a new user', async () => { diff --git a/test/testPush.test.ts b/test/testPush.test.ts index 8bf85788d..307a57727 100644 --- a/test/testPush.test.ts +++ b/test/testPush.test.ts @@ -123,7 +123,7 @@ describe('Push API', () => { await db.deletePush(TEST_PUSH.id); vi.resetModules(); - await Service.httpServer.close(); + await Service.httpServer?.close(); const res = await request(app).post('/api/auth/logout').set('Cookie', `${cookie}`); expect(res.status).toBe(200); @@ -292,7 +292,7 @@ describe('Push API', () => { it('should return 401 if not logged in when approving a push', async () => { const res = await request(app) .post(`/api/v1/push/${TEST_PUSH.id}/authorise`) - .send({ reason: 'Testing approval' }); + .send({ params: { attestation: [] } }); expect(res.status).toBe(401); expect(res.body.message).toBe('Not logged in'); }); @@ -315,7 +315,11 @@ describe('Push API', () => { .set('Cookie', `${cookie}`) .send({}); expect(res.status).toBe(400); - expect(res.body.message).toBe('Rejection reason is required'); + expect(res.body.message).toBe('Validation failed'); + expect(res.body.details).toBeDefined(); + console.log(res.body.details); + expect(res.body.details['body.reason']).toBeDefined(); + expect(res.body.details['body.reason'].message).toBe("'reason' is required"); }); it('should NOT allow an authorizer to reject a push with empty reason', async () => { diff --git a/test/testRepoApi.test.ts b/test/testRepoApi.test.ts index 11e4cf6bf..cd9eb8487 100644 --- a/test/testRepoApi.test.ts +++ b/test/testRepoApi.test.ts @@ -504,7 +504,10 @@ describe('repo routes - edge cases', () => { }); expect(res.status).toBe(400); - expect(res.body.message).toBe('Repository url is required'); + expect(res.body.message).toBe('Validation failed'); + expect(res.body.details).toBeDefined(); + expect(res.body.details['body.url']).toBeDefined(); + expect(res.body.details['body.url'].message).toBe("'url' is required"); }); it('should return 400 when repo url is invalid', async () => { @@ -678,6 +681,6 @@ describe('repo routes - edge cases', () => { await cleanupRepo(TEST_REPO.url); await db.deleteUser('testuser'); await db.deleteUser('nonadmin'); - await Service.httpServer.close(); + await Service.httpServer?.close(); }); }); diff --git a/tsconfig.json b/tsconfig.json index 331c876ef..6ba296d3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,9 @@ "outDir": "./dist", "rootDir": "./src", "noEmit": false, - "types": ["node"] + "types": ["node"], + + "experimentalDecorators": true }, "include": ["src"], "exclude": ["node_modules", "dist", "**/*.test.ts"] diff --git a/tsoa.json b/tsoa.json new file mode 100644 index 000000000..8000b462f --- /dev/null +++ b/tsoa.json @@ -0,0 +1,26 @@ +{ + "entryFile": "src/app.ts", + "noImplicitAdditionalProperties": "ignore", + "controllerPathGlobs": ["src/service/controllers/**/*Controller.ts"], + "spec": { + "outputDirectory": "dist", + "specVersion": 3, + "securityDefinitions": { + "jwt": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "info": { + "title": "Git Proxy API", + "version": "1.0.0" + } + }, + "routes": { + "routesDir": "src/service", + "routesFileName": "generatedRoutes.ts", + "authenticationModule": "./src/service/authentication", + "middleware": "express" + } +} From 19cb00c0e402abf94b6618ce59a5cb4ee8337aef Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Wed, 22 Apr 2026 02:51:48 +0200 Subject: [PATCH 02/18] fix: fix review comments --- docs/Architecture.md | 2 +- src/service/authentication.ts | 1 - src/service/controllers/AuthController.ts | 91 +++++----------- src/service/controllers/HealthController.ts | 6 +- src/service/controllers/HomeController.ts | 7 +- src/service/controllers/PushController.ts | 64 ++++------- src/service/controllers/RepoController.ts | 58 +++++----- src/service/controllers/UserController.ts | 7 +- src/service/decorators/response.types.ts | 25 +++++ src/service/interfaces/auth.interfaces.ts | 57 ++++++++++ .../common.interfaces.ts} | 0 src/service/interfaces/health.interfaces.ts | 19 ++++ src/service/interfaces/home.interfaces.ts | 21 ++++ src/service/interfaces/push.interfaces.ts | 28 +++++ src/service/interfaces/repo.interfaces.ts | 31 ++++++ src/service/passport/jwtAuthHandler.ts | 101 ------------------ test/services/routes/auth.test.ts | 2 - test/services/routes/users.test.ts | 2 - test/testJwtAuthHandler.test.ts | 73 ++++++------- 19 files changed, 293 insertions(+), 302 deletions(-) create mode 100644 src/service/decorators/response.types.ts create mode 100644 src/service/interfaces/auth.interfaces.ts rename src/service/{models.ts => interfaces/common.interfaces.ts} (100%) create mode 100644 src/service/interfaces/health.interfaces.ts create mode 100644 src/service/interfaces/home.interfaces.ts create mode 100644 src/service/interfaces/push.interfaces.ts create mode 100644 src/service/interfaces/repo.interfaces.ts delete mode 100644 src/service/passport/jwtAuthHandler.ts diff --git a/docs/Architecture.md b/docs/Architecture.md index 7f49ebe62..f7021a00c 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -432,7 +432,7 @@ Allows defining ways to authenticate to the API. This is useful for securing cus If `apiAuthentication` is left empty, API endpoints will be publicly accesible. -Currently, only JWT auth is supported. This is implemented via the [`jwtAuthHandler` middleware](/src/service/passport/jwtAuthHandler.ts). Aside of validating incoming access tokens, it can also assign roles based on the token payload. +Currently, only JWT auth is supported. This is implemented via the [`@Security('jwt')` decorator](/src/service/authentication.ts). Aside of validating incoming access tokens, it can also assign roles based on the token payload. ##### Setting up JWT Authentication diff --git a/src/service/authentication.ts b/src/service/authentication.ts index bd378f54f..c0b4e4489 100644 --- a/src/service/authentication.ts +++ b/src/service/authentication.ts @@ -28,7 +28,6 @@ import type { RoleMapping } from '../config/generated/config'; export async function expressAuthentication( request: Request, securityName: string, - _scopes?: string[], ): Promise { if (securityName === 'jwt') { diff --git a/src/service/controllers/AuthController.ts b/src/service/controllers/AuthController.ts index 44ff0de38..c3fe1b14f 100644 --- a/src/service/controllers/AuthController.ts +++ b/src/service/controllers/AuthController.ts @@ -15,18 +15,7 @@ */ import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'; -import { - Body, - Controller, - Get, - Middlewares, - Post, - Request, - Res, - Route, - Tags, - TsoaResponse, -} from 'tsoa'; +import { Body, Controller, Get, Middlewares, Post, Request, Res, Route, Tags } from 'tsoa'; import { getPassport, authStrategies } from '../passport'; import { getAuthMethods } from '../../config'; import * as db from '../../db'; @@ -37,6 +26,22 @@ import { AuthenticationElement } from '../../config/generated/config'; import { isAdminUser, toPublicUser } from '../routes/utils'; import { handleErrorAndLog } from '../../utils/errors'; import { PublicUser } from '../../db/types'; +import { + AuthResources, + AuthConfigResponse, + LoginResponse, + LogoutResponse, + CreateUserResponse, + GitAccountBody, + CreateUserBody, +} from '../interfaces/auth.interfaces'; +import { + ForbiddenResponse, + InternalServerErrorResponse, + NotFoundResponse, + UnauthorisedResponse, + ValidationErrorResponse, +} from '../decorators/response.types'; const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost', GIT_PROXY_UI_PORT: uiPort = 3000 } = process.env; @@ -111,48 +116,6 @@ export function oidcCallbackMiddleware( )(req, res, next); } -// ---------- Response types ---------- - -interface AuthResources { - login: { action: 'post'; uri: string }; - profile: { action: 'get'; uri: string }; - logout: { action: 'post'; uri: string }; -} - -interface AuthConfigResponse { - usernamePasswordMethod: string | null; - otherMethods: string[]; -} - -interface LoginResponse { - message: 'success'; - user: PublicUser; -} - -interface LogoutResponse { - isAuth: boolean; - user: Express.User | undefined; -} - -interface CreateUserResponse { - message: string; - username: string; -} - -interface GitAccountBody { - username?: string; - id?: string; - gitAccount: string; -} - -interface CreateUserBody { - username: string; - password: string; - email: string; - gitAccount: string; - admin?: boolean; -} - /** * Authentication endpoints. */ @@ -187,8 +150,6 @@ export class AuthController extends Controller { }; } - // TODO: provide separate auth endpoints for each auth strategy or chain compatibile auth strategies - // TODO: if providing separate auth methods, inform the frontend so it has relevant UI elements and appropriate client-side behavior /** * Authenticates the user with a username/password strategy. * The appropriate passport strategy is selected dynamically based on configuration. @@ -249,8 +210,8 @@ export class AuthController extends Controller { @Get('/profile') public async getProfile( @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() notFoundResponse: NotFoundResponse, ): Promise { if (!req.user) { return unauthorisedResponse(401, { message: 'Not logged in' }); @@ -272,11 +233,11 @@ export class AuthController extends Controller { public async updateGitAccount( @Body() body: GitAccountBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, - @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, - @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, - @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() notFoundResponse: NotFoundResponse, + @Res() validationErrorResponse: ValidationErrorResponse, + @Res() forbiddenResponse: ForbiddenResponse, + @Res() internalServerErrorResponse: InternalServerErrorResponse, ): Promise { if (!req.user) { return unauthorisedResponse(401, { message: 'Not logged in' }); @@ -321,8 +282,8 @@ export class AuthController extends Controller { public async createUser( @Body() body: CreateUserBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<403, { message: string }>, - @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + @Res() unauthorisedResponse: ForbiddenResponse, + @Res() internalServerErrorResponse: InternalServerErrorResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(403, { message: 'Not authorized to create users' }); diff --git a/src/service/controllers/HealthController.ts b/src/service/controllers/HealthController.ts index 76c1238e3..76d8eac0b 100644 --- a/src/service/controllers/HealthController.ts +++ b/src/service/controllers/HealthController.ts @@ -15,11 +15,7 @@ */ import { Controller, Get, Route, Tags } from 'tsoa'; - -interface HealthResponse { - message: 'ok'; -} - +import { HealthResponse } from '../interfaces/health.interfaces'; /** * Service health check. */ diff --git a/src/service/controllers/HomeController.ts b/src/service/controllers/HomeController.ts index 89279109a..3f003ba13 100644 --- a/src/service/controllers/HomeController.ts +++ b/src/service/controllers/HomeController.ts @@ -15,12 +15,7 @@ */ import { Controller, Get, Route, Tags } from 'tsoa'; - -interface ApiResources { - healthcheck: string; - push: string; - auth: string; -} +import { ApiResources } from '../interfaces/home.interfaces'; /** * API home — lists available resource URIs. diff --git a/src/service/controllers/PushController.ts b/src/service/controllers/PushController.ts index e034b5678..313d931c7 100644 --- a/src/service/controllers/PushController.ts +++ b/src/service/controllers/PushController.ts @@ -14,19 +14,7 @@ * limitations under the License. */ -import { - Body, - Controller, - Get, - Path, - Post, - Request, - Res, - Route, - Security, - Tags, - TsoaResponse, -} from 'tsoa'; +import { Body, Controller, Get, Path, Post, Request, Res, Route, Security, Tags } from 'tsoa'; import type { Request as ExpressRequest } from 'express'; import * as db from '../../db'; import { PushQuery } from '../../db/types'; @@ -34,27 +22,17 @@ import { AttestationConfig } from '../../config/generated/config'; import { getAttestationConfig } from '../../config'; import { Action } from '../../proxy/actions/Action'; import { AttestationAnswer, Rejection } from '../../proxy/processors/types'; -import { MessageResponse } from '../models'; - -interface RejectBody { - /** The reason for rejecting the push request. */ - reason: string; -} - -/** Attestation answer as sent by the UI (may include display-only fields). */ -interface AttestationAnswerRequest { - label: string; - checked: boolean; -} - -interface AuthoriseBody { - params: { - attestation: AttestationAnswerRequest[]; - }; -} +import { MessageResponse } from '../interfaces/common.interfaces'; +import { RejectBody, AuthoriseBody } from '../interfaces/push.interfaces'; +import { + ForbiddenResponse, + NotFoundResponse, + UnauthorisedResponse, + ValidationErrorResponse, +} from '../decorators/response.types'; /** - * Push request management (JWT-protected). + * Push request management. */ @Route('api/v1/push') @Security('jwt') @@ -88,7 +66,7 @@ export class PushController extends Controller { @Get('/{id}') public async getPush( @Path() id: string, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() notFoundResponse: NotFoundResponse, ): Promise { const push = await db.getPush(id); if (!push) { @@ -105,10 +83,10 @@ export class PushController extends Controller { @Path() id: string, @Body() body: RejectBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, - @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() validationErrorResponse: ValidationErrorResponse, + @Res() notFoundResponse: NotFoundResponse, + @Res() forbiddenResponse: ForbiddenResponse, ): Promise { if (!req.user) { return unauthorisedResponse(401, { message: 'Not logged in' }); @@ -180,10 +158,10 @@ export class PushController extends Controller { @Path() id: string, @Body() body: AuthoriseBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, - @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() validationErrorResponse: ValidationErrorResponse, + @Res() notFoundResponse: NotFoundResponse, + @Res() forbiddenResponse: ForbiddenResponse, ): Promise { if (!req.user) { return unauthorisedResponse(401, { message: 'Not logged in' }); @@ -250,8 +228,8 @@ export class PushController extends Controller { public async cancelPush( @Path() id: string, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() forbiddenResponse: TsoaResponse<403, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() forbiddenResponse: ForbiddenResponse, ): Promise { if (!req.user) { return unauthorisedResponse(401, { message: 'Not logged in' }); diff --git a/src/service/controllers/RepoController.ts b/src/service/controllers/RepoController.ts index 52a25e3af..d6551f465 100644 --- a/src/service/controllers/RepoController.ts +++ b/src/service/controllers/RepoController.ts @@ -28,32 +28,26 @@ import { Security, Tags, } from 'tsoa'; -import type { TsoaResponse } from 'tsoa'; import type { Request as ExpressRequest } from 'express'; import * as db from '../../db'; -import { Repo, RepoQuery } from '../../db/types'; +import { RepoQuery } from '../../db/types'; import { getProxyURL } from '../urls'; import { isAdminUser } from '../routes/utils'; import { getProxy } from '../proxyStore'; import { handleErrorAndLog } from '../../utils/errors'; -import { MessageResponse } from '../models'; - -interface UsernameBody { - username: string; -} - -interface CreateRepoBody { - url: string; - name: string; - project: string; -} - -interface RepoWithProxy extends Repo { - proxyURL: string; -} +import { MessageResponse } from '../interfaces/common.interfaces'; +import { UsernameBody, CreateRepoBody, RepoWithProxy } from '../interfaces/repo.interfaces'; +import { + ConflictResponse, + InternalServerErrorResponse, + NotFoundResponse, + UnauthorisedResponse, + UserNotFoundResponse, + ValidationErrorResponse, +} from '../decorators/response.types'; /** - * Repository management (JWT-protected). + * Repository management. */ @Route('api/v1/repo') @Security('jwt') @@ -89,7 +83,7 @@ export class RepoController extends Controller { public async getRepo( @Path() id: string, @Request() req: ExpressRequest, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() notFoundResponse: NotFoundResponse, ): Promise { const proxyURL = getProxyURL(req); const repo = await db.getRepoById(id); @@ -106,10 +100,10 @@ export class RepoController extends Controller { public async createRepo( @Body() body: CreateRepoBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() validationErrorResponse: TsoaResponse<400, { message: string }>, - @Res() conflictResponse: TsoaResponse<409, { message: string }>, - @Res() internalServerErrorResponse: TsoaResponse<500, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() validationErrorResponse: ValidationErrorResponse, + @Res() conflictResponse: ConflictResponse, + @Res() internalServerErrorResponse: InternalServerErrorResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { @@ -170,8 +164,8 @@ export class RepoController extends Controller { @Path() id: string, @Body() body: UsernameBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() userNotFoundResponse: UserNotFoundResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { @@ -197,8 +191,8 @@ export class RepoController extends Controller { @Path() id: string, @Body() body: UsernameBody, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() userNotFoundResponse: UserNotFoundResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { @@ -223,8 +217,8 @@ export class RepoController extends Controller { @Path() id: string, @Path() username: string, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() userNotFoundResponse: UserNotFoundResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { @@ -249,8 +243,8 @@ export class RepoController extends Controller { @Path() id: string, @Path() username: string, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, - @Res() userNotFoundResponse: TsoaResponse<400, { error: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, + @Res() userNotFoundResponse: UserNotFoundResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { @@ -274,7 +268,7 @@ export class RepoController extends Controller { public async deleteRepo( @Path() id: string, @Request() req: ExpressRequest, - @Res() unauthorisedResponse: TsoaResponse<401, { message: string }>, + @Res() unauthorisedResponse: UnauthorisedResponse, ): Promise { if (!isAdminUser(req.user)) { return unauthorisedResponse(401, { diff --git a/src/service/controllers/UserController.ts b/src/service/controllers/UserController.ts index 99b783906..82e596b59 100644 --- a/src/service/controllers/UserController.ts +++ b/src/service/controllers/UserController.ts @@ -14,13 +14,14 @@ * limitations under the License. */ -import { Controller, Get, Path, Res, Route, Security, Tags, TsoaResponse } from 'tsoa'; +import { Controller, Get, Path, Res, Route, Security, Tags } from 'tsoa'; import * as db from '../../db'; import { PublicUser } from '../../db/types'; import { toPublicUser } from '../routes/utils'; +import { NotFoundResponse } from '../decorators/response.types'; /** - * User listing (JWT-protected). + * User listing. */ @Route('api/v1/user') @Security('jwt') @@ -42,7 +43,7 @@ export class UserController extends Controller { @Get('/{id}') public async getUser( @Path() id: string, - @Res() notFoundResponse: TsoaResponse<404, { message: string }>, + @Res() notFoundResponse: NotFoundResponse, ): Promise { const username = id.toLowerCase(); console.log(`Retrieving details for user: ${username}`); diff --git a/src/service/decorators/response.types.ts b/src/service/decorators/response.types.ts new file mode 100644 index 000000000..478a145dd --- /dev/null +++ b/src/service/decorators/response.types.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TsoaResponse } from 'tsoa'; + +export type UnauthorisedResponse = TsoaResponse<401, { message: string }>; +export type ForbiddenResponse = TsoaResponse<403, { message: string }>; +export type NotFoundResponse = TsoaResponse<404, { message: string }>; +export type ValidationErrorResponse = TsoaResponse<400, { message: string }>; +export type ConflictResponse = TsoaResponse<409, { message: string }>; +export type InternalServerErrorResponse = TsoaResponse<500, { message: string }>; +export type UserNotFoundResponse = TsoaResponse<400, { error: string }>; diff --git a/src/service/interfaces/auth.interfaces.ts b/src/service/interfaces/auth.interfaces.ts new file mode 100644 index 000000000..6a00f490f --- /dev/null +++ b/src/service/interfaces/auth.interfaces.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PublicUser } from '../../db/types'; + +export interface AuthResources { + login: { action: 'post'; uri: string }; + profile: { action: 'get'; uri: string }; + logout: { action: 'post'; uri: string }; +} + +export interface AuthConfigResponse { + usernamePasswordMethod: string | null; + otherMethods: string[]; +} + +export interface LoginResponse { + message: 'success'; + user: PublicUser; +} + +export interface LogoutResponse { + isAuth: boolean; + user: Express.User | undefined; +} + +export interface CreateUserResponse { + message: string; + username: string; +} + +export interface GitAccountBody { + username?: string; + id?: string; + gitAccount: string; +} + +export interface CreateUserBody { + username: string; + password: string; + email: string; + gitAccount: string; + admin?: boolean; +} diff --git a/src/service/models.ts b/src/service/interfaces/common.interfaces.ts similarity index 100% rename from src/service/models.ts rename to src/service/interfaces/common.interfaces.ts diff --git a/src/service/interfaces/health.interfaces.ts b/src/service/interfaces/health.interfaces.ts new file mode 100644 index 000000000..e105259fe --- /dev/null +++ b/src/service/interfaces/health.interfaces.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface HealthResponse { + message: 'ok'; +} diff --git a/src/service/interfaces/home.interfaces.ts b/src/service/interfaces/home.interfaces.ts new file mode 100644 index 000000000..5914707cf --- /dev/null +++ b/src/service/interfaces/home.interfaces.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ApiResources { + healthcheck: string; + push: string; + auth: string; +} diff --git a/src/service/interfaces/push.interfaces.ts b/src/service/interfaces/push.interfaces.ts new file mode 100644 index 000000000..94bb90344 --- /dev/null +++ b/src/service/interfaces/push.interfaces.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AttestationAnswer } from '../../proxy/processors/types'; + +export interface RejectBody { + /** The reason for rejecting the push request. */ + reason: string; +} + +export interface AuthoriseBody { + params: { + attestation: AttestationAnswer[]; + }; +} diff --git a/src/service/interfaces/repo.interfaces.ts b/src/service/interfaces/repo.interfaces.ts new file mode 100644 index 000000000..4a6238a11 --- /dev/null +++ b/src/service/interfaces/repo.interfaces.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Repo } from '../../db/types'; + +export interface UsernameBody { + username: string; +} + +export interface CreateRepoBody { + url: string; + name: string; + project: string; +} + +export interface RepoWithProxy extends Repo { + proxyURL: string; +} diff --git a/src/service/passport/jwtAuthHandler.ts b/src/service/passport/jwtAuthHandler.ts deleted file mode 100644 index d03fe5555..000000000 --- a/src/service/passport/jwtAuthHandler.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2026 GitProxy Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assignRoles, validateJwt } from './jwtUtils'; -import type { Request, Response, NextFunction } from 'express'; -import { getAPIAuthMethods } from '../../config'; -import { - AuthenticationElement, - JwtConfig, - RoleMapping, - AuthenticationElementType, -} from '../../config/generated/config'; - -export const type = 'jwt'; - -export const jwtAuthHandler = (overrideConfig: JwtConfig | null = null) => { - return async (req: Request, res: Response, next: NextFunction): Promise => { - const apiAuthMethods: AuthenticationElement[] = overrideConfig - ? [{ type: 'jwt' as AuthenticationElementType, enabled: true, jwtConfig: overrideConfig }] - : getAPIAuthMethods(); - - const jwtAuthMethod = apiAuthMethods.find((method) => method.type.toLowerCase() === type); - - if (!jwtAuthMethod || !jwtAuthMethod.enabled) { - return next(); - } - - if (req.isAuthenticated && req.isAuthenticated()) { - return next(); - } - - const token = req.header('Authorization'); - if (!token) { - res.status(401).send('No token provided\n'); - return; - } - - if (!jwtAuthMethod.jwtConfig) { - res.status(500).send({ - message: 'JWT configuration is missing\n', - }); - console.log('JWT configuration is missing\n'); - return; - } - - const config = jwtAuthMethod.jwtConfig!; - const { clientID, authorityURL, expectedAudience, roleMapping } = config; - const audience = expectedAudience || clientID; - - if (!authorityURL) { - res.status(500).send({ - message: 'OIDC authority URL is not configured\n', - }); - console.log('OIDC authority URL is not configured\n'); - return; - } - - if (!clientID) { - res.status(500).send({ - message: 'OIDC client ID is not configured\n', - }); - console.log('OIDC client ID is not configured\n'); - return; - } - - const tokenParts = token.split(' '); - const accessToken = tokenParts.length === 2 ? tokenParts[1] : tokenParts[0]; - - const { verifiedPayload, error } = await validateJwt( - accessToken, - authorityURL, - audience, - clientID, - ); - - if (error || !verifiedPayload) { - res.status(401).send(error || 'JWT validation failed\n'); - console.log('JWT validation failed\n'); - return; - } - - req.user = verifiedPayload; - assignRoles(roleMapping as RoleMapping, verifiedPayload, req.user); - - console.log('JWT validation successful\n'); - next(); - }; -}; diff --git a/test/services/routes/auth.test.ts b/test/services/routes/auth.test.ts index 3296761d6..34597919e 100644 --- a/test/services/routes/auth.test.ts +++ b/test/services/routes/auth.test.ts @@ -33,12 +33,10 @@ const newApp = (username?: string, isAdmin = false): Express => { if (username) { app.use((req, _res, next) => { req.user = { username, admin: isAdmin }; - (req as any).isAuthenticated = () => true; next(); }); } else { app.use((_req, _res, next) => { - (_req as any).isAuthenticated = () => false; next(); }); } diff --git a/test/services/routes/users.test.ts b/test/services/routes/users.test.ts index 9ec661e26..c326e67e8 100644 --- a/test/services/routes/users.test.ts +++ b/test/services/routes/users.test.ts @@ -26,10 +26,8 @@ describe('Users API', () => { beforeEach(() => { app = express(); app.use(express.json()); - // Pre-authenticate so JWT security allows through in tests. app.use((req, _res, next) => { req.user = { username: 'testuser' }; - (req as any).isAuthenticated = () => true; next(); }); RegisterRoutes(app); diff --git a/test/testJwtAuthHandler.test.ts b/test/testJwtAuthHandler.test.ts index 7d41872e1..b2d14fade 100644 --- a/test/testJwtAuthHandler.test.ts +++ b/test/testJwtAuthHandler.test.ts @@ -16,12 +16,12 @@ import axios from 'axios'; import crypto from 'crypto'; -import { NextFunction } from 'express'; import jwt, { JwtPayload } from 'jsonwebtoken'; import { describe, it, expect, vi, beforeEach, afterEach, MockInstance } from 'vitest'; import { assignRoles, getJwks, validateJwt } from '../src/service/passport/jwtUtils'; -import { jwtAuthHandler } from '../src/service/passport/jwtAuthHandler'; +import { expressAuthentication } from '../src/service/authentication'; +import * as configModule from '../src/config'; import { JwtConfig, RoleMapping } from '../src/config/generated/config'; function generateRsaKeyPair() { @@ -189,17 +189,13 @@ describe('JWT', () => { }); }); - describe('jwtAuthHandler', () => { + describe('expressAuthentication', () => { let req: any; - let res: any; - let next: NextFunction; let jwtConfig: JwtConfig; - let validVerifyResponse: JwtPayload; + let mockGetAPIAuthMethods: MockInstance; beforeEach(() => { req = { header: vi.fn(), isAuthenticated: vi.fn(), user: {} }; - res = { status: vi.fn().mockReturnThis(), send: vi.fn() }; - next = vi.fn(); jwtConfig = { clientID: 'client-id', @@ -208,62 +204,57 @@ describe('JWT', () => { roleMapping: { admin: { admin: 'admin' } }, }; - validVerifyResponse = { - header: { kid: '123' }, - azp: 'client-id', - sub: 'user123', - admin: 'admin', - }; + mockGetAPIAuthMethods = vi + .spyOn(configModule, 'getAPIAuthMethods') + .mockReturnValue([{ type: 'jwt', enabled: true, jwtConfig }] as any); }); afterEach(() => vi.restoreAllMocks()); - it('should call next if user is authenticated', async () => { + it('should return user if already authenticated via session', async () => { req.isAuthenticated.mockReturnValue(true); - await jwtAuthHandler()(req, res, next); - expect(next).toHaveBeenCalledOnce(); + const result = await expressAuthentication(req, 'jwt'); + expect(result).toBe(req.user); }); - it('should return 401 if no token provided', async () => { - req.header.mockReturnValue(null); - await jwtAuthHandler(jwtConfig)(req, res, next); + it('should return undefined if JWT auth method is not configured', async () => { + mockGetAPIAuthMethods.mockReturnValue([]); + req.isAuthenticated.mockReturnValue(false); + const result = await expressAuthentication(req, 'jwt'); + expect(result).toBeUndefined(); + }); - expect(res.status).toHaveBeenCalledWith(401); - expect(res.send).toHaveBeenCalledWith('No token provided\n'); + it('should throw 401 if no token provided', async () => { + req.isAuthenticated.mockReturnValue(false); + req.header.mockReturnValue(null); + await expect(expressAuthentication(req, 'jwt')).rejects.toMatchObject({ status: 401 }); }); - it('should return 500 if authorityURL not configured', async () => { + it('should throw 500 if authorityURL not configured', async () => { + req.isAuthenticated.mockReturnValue(false); req.header.mockReturnValue('Bearer fake-token'); jwtConfig.authorityURL = null; - vi.spyOn(jwt, 'verify').mockReturnValue(validVerifyResponse); - - await jwtAuthHandler(jwtConfig)(req, res, next); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.send).toHaveBeenCalledWith({ message: 'OIDC authority URL is not configured\n' }); + await expect(expressAuthentication(req, 'jwt')).rejects.toMatchObject({ status: 500 }); }); - it('should return 500 if clientID not configured', async () => { + it('should throw 500 if clientID not configured', async () => { + req.isAuthenticated.mockReturnValue(false); req.header.mockReturnValue('Bearer fake-token'); jwtConfig.clientID = null; - vi.spyOn(jwt, 'verify').mockReturnValue(validVerifyResponse); - - await jwtAuthHandler(jwtConfig)(req, res, next); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.send).toHaveBeenCalledWith({ message: 'OIDC client ID is not configured\n' }); + await expect(expressAuthentication(req, 'jwt')).rejects.toMatchObject({ status: 500 }); }); - it('should return 401 if JWT validation fails', async () => { + it('should throw 401 if JWT validation fails', async () => { + req.isAuthenticated.mockReturnValue(false); req.header.mockReturnValue('Bearer fake-token'); vi.spyOn(jwt, 'verify').mockImplementation(() => { throw new Error('Invalid token'); }); + await expect(expressAuthentication(req, 'jwt')).rejects.toMatchObject({ status: 401 }); + }); - await jwtAuthHandler(jwtConfig)(req, res, next); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.send).toHaveBeenCalledWith(expect.stringMatching(/Invalid JWT:/)); + it('should throw 401 for unknown security scheme', async () => { + await expect(expressAuthentication(req, 'unknown')).rejects.toMatchObject({ status: 401 }); }); }); }); From 9520a31dce588fcce29288d41c14c7141a5fb443 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Wed, 22 Apr 2026 02:56:10 +0200 Subject: [PATCH 03/18] fix: regenerate package-lock.json --- package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4b126779e..4931b4dc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15762,7 +15762,6 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" From c9704e43f9175080043bd8a7fcac7acb0ddf6811 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 13 May 2026 20:02:43 +0200 Subject: [PATCH 04/18] Update package.json Co-authored-by: Fabio Vincenzi <93596376+fabiovincenzi@users.noreply.github.com> Signed-off-by: Andrew --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f04c57e8d..419d9f74c 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "build-ui": "vite build", "build-tsoa": "tsoa spec-and-routes", "check-types": "tsc", - "check-types:server": "tsc --project tsconfig.publish.json --noEmit", + "check-types:server": "npm run build-tsoa && tsc --project tsconfig.publish.json --noEmit", "test-shuffle": "NODE_ENV=test vitest --run --dir ./test --sequence.shuffle", "test": "cross-env NODE_ENV=test vitest --run --dir ./test", "test:e2e": "vitest run --config vitest.config.e2e.ts", From c4a752cd95179162ee33100a29492080cf22a43d Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Wed, 13 May 2026 20:41:19 +0200 Subject: [PATCH 05/18] fix(service): fix failing git-proxy-cli test on /create-user --- src/service/controllers/AuthController.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/service/controllers/AuthController.ts b/src/service/controllers/AuthController.ts index c3fe1b14f..ed8b885cf 100644 --- a/src/service/controllers/AuthController.ts +++ b/src/service/controllers/AuthController.ts @@ -283,6 +283,7 @@ export class AuthController extends Controller { @Body() body: CreateUserBody, @Request() req: ExpressRequest, @Res() unauthorisedResponse: ForbiddenResponse, + @Res() validationErrorResponse: ValidationErrorResponse, @Res() internalServerErrorResponse: InternalServerErrorResponse, ): Promise { if (!isAdminUser(req.user)) { @@ -291,6 +292,12 @@ export class AuthController extends Controller { const { username, password, email, gitAccount, admin: isAdmin = false } = body; + if (!username || !password || !email || !gitAccount) { + return validationErrorResponse(400, { + message: 'Missing required fields: username, password, email, and gitAccount are required', + }); + } + try { await db.createUser(username, password, email, gitAccount, isAdmin); this.setStatus(201); From 40007a91152b8f01bba14d11d2932cf4f77b7b5b Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 18 May 2026 03:52:50 +0200 Subject: [PATCH 06/18] feat(service): add generatedRoutes.ts to repo and add pre-commit check --- .gitignore | 2 - eslint.config.mjs | 1 + package.json | 3 + scripts/check-tsoa-routes.js | 39 + src/service/generatedRoutes.ts | 1380 ++++++++++++++++++++++++++++++++ 5 files changed, 1423 insertions(+), 2 deletions(-) create mode 100644 scripts/check-tsoa-routes.js create mode 100644 src/service/generatedRoutes.ts diff --git a/.gitignore b/.gitignore index b1895b1c8..a04e90d2c 100644 --- a/.gitignore +++ b/.gitignore @@ -277,5 +277,3 @@ website/.docusaurus # Generated from testing /test/fixtures/test-package/package-lock.json -# tsoa generated files (regenerated by `npm run build-tsoa`) -src/service/generatedRoutes.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index b074b622f..9f98185de 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -52,6 +52,7 @@ export default defineConfig( // generated files we don't control '**/package-lock.json', 'src/config/generated/**', + 'src/service/generatedRoutes.ts', // has it's own eslint 'experimental/license-inventory', // vendored code we're not changing diff --git a/package.json b/package.json index 4b5e5c654..12dc311a1 100644 --- a/package.json +++ b/package.json @@ -223,6 +223,9 @@ ], "test/**/*.{js,jsx,ts,tsx,json}": [ "eslint --fix" + ], + "{src/service/controllers/**/*Controller.ts,src/service/authentication.ts,src/service/generatedRoutes.ts,src/app.ts,tsoa.json}": [ + "node scripts/check-tsoa-routes.js --" ] } } diff --git a/scripts/check-tsoa-routes.js b/scripts/check-tsoa-routes.js new file mode 100644 index 000000000..277b1cb18 --- /dev/null +++ b/scripts/check-tsoa-routes.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Invoked by lint-staged when any TSOA input is staged. Regenerates + * src/service/generatedRoutes.ts via `npm run build-tsoa` and stages it + * so the commit always includes routes in sync with their inputs — + * the same fix-then-continue behaviour as `prettier --write`. + */ +const { spawnSync } = require('node:child_process'); + +const build = spawnSync('npm run --silent build-tsoa', { + stdio: 'inherit', + shell: true, +}); +if (build.status !== 0) { + process.exit(build.status ?? 1); +} + +const add = spawnSync('git add src/service/generatedRoutes.ts', { + stdio: 'inherit', + shell: true, +}); +process.exit(add.status ?? 0); diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts new file mode 100644 index 000000000..6f3708058 --- /dev/null +++ b/src/service/generatedRoutes.ts @@ -0,0 +1,1380 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import type { TsoaRoute } from '@tsoa/runtime'; +import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { UserController } from './controllers/UserController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { RepoController } from './controllers/RepoController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { PushController } from './controllers/PushController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { HomeController } from './controllers/HomeController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { HealthController } from './controllers/HealthController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ConfigController } from './controllers/ConfigController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { AuthController } from './controllers/AuthController'; +import { expressAuthentication } from './authentication'; +// @ts-ignore - no great way to install types from subpackage +import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; + +const expressAuthenticationRecasted = expressAuthentication as (req: ExRequest, securityName: string, scopes?: string[], res?: ExResponse) => Promise; + + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "PublicUser": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, + "displayName": {"dataType":"string","required":true}, + "email": {"dataType":"string","required":true}, + "title": {"dataType":"string","required":true}, + "gitAccount": {"dataType":"string","required":true}, + "admin": {"dataType":"boolean","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RepoWithProxy": { + "dataType": "refObject", + "properties": { + "project": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, + "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, + "_id": {"dataType":"string"}, + "proxyURL": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MessageResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateRepoBody": { + "dataType": "refObject", + "properties": { + "url": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "project": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UsernameBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Step": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "stepName": {"dataType":"string","required":true}, + "content": {"dataType":"any","required":true}, + "error": {"dataType":"boolean","required":true}, + "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "blocked": {"dataType":"boolean","required":true}, + "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "logs": {"dataType":"array","array":{"dataType":"string"},"default":[]}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CommitData": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true},"commitTimestamp":{"dataType":"string","required":true},"committerEmail":{"dataType":"string","required":true},"authorEmail":{"dataType":"string","required":true},"committer":{"dataType":"string","required":true},"author":{"dataType":"string","required":true},"parent":{"dataType":"string","required":true},"tree":{"dataType":"string","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationBase": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"automated":{"dataType":"boolean"},"timestamp":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},"reviewer":{"dataType":"nestedObjectLiteral","nestedProperties":{"email":{"dataType":"string","required":true},"username":{"dataType":"string","required":true}},"required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationAnswer": { + "dataType": "refObject", + "properties": { + "label": {"dataType":"string","required":true}, + "checked": {"dataType":"boolean","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CompletedAttestation": { + "dataType": "refAlias", + "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"answers":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Rejection": { + "dataType": "refAlias", + "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string","required":true}}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Action": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "type": {"dataType":"string","required":true}, + "method": {"dataType":"string","required":true}, + "timestamp": {"dataType":"double","required":true}, + "project": {"dataType":"string","required":true}, + "repoName": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, + "repo": {"dataType":"string","required":true}, + "steps": {"dataType":"array","array":{"dataType":"refObject","ref":"Step"},"default":[]}, + "error": {"dataType":"boolean","default":false}, + "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, + "blocked": {"dataType":"boolean","default":false}, + "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, + "allowPush": {"dataType":"boolean","default":false}, + "authorised": {"dataType":"boolean","default":false}, + "canceled": {"dataType":"boolean","default":false}, + "rejected": {"dataType":"boolean","default":false}, + "autoApproved": {"dataType":"boolean","default":false}, + "autoRejected": {"dataType":"boolean","default":false}, + "commitData": {"dataType":"array","array":{"dataType":"refAlias","ref":"CommitData"},"default":[]}, + "commitFrom": {"dataType":"string"}, + "commitTo": {"dataType":"string"}, + "branch": {"dataType":"string"}, + "message": {"dataType":"string"}, + "author": {"dataType":"string"}, + "user": {"dataType":"string"}, + "userEmail": {"dataType":"string"}, + "attestation": {"ref":"CompletedAttestation"}, + "rejection": {"ref":"Rejection"}, + "lastStep": {"ref":"Step"}, + "proxyGitPath": {"dataType":"string"}, + "newIdxFiles": {"dataType":"array","array":{"dataType":"string"}}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RejectBody": { + "dataType": "refObject", + "properties": { + "reason": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthoriseBody": { + "dataType": "refObject", + "properties": { + "params": {"dataType":"nestedObjectLiteral","nestedProperties":{"attestation":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}},"required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResources": { + "dataType": "refObject", + "properties": { + "healthcheck": {"dataType":"string","required":true}, + "push": {"dataType":"string","required":true}, + "auth": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "HealthResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"enum","enums":["ok"],"required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Link": { + "dataType": "refObject", + "properties": { + "text": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "QuestionTooltip": { + "dataType": "refObject", + "properties": { + "links": {"dataType":"array","array":{"dataType":"refObject","ref":"Link"}}, + "text": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Question": { + "dataType": "refObject", + "properties": { + "label": {"dataType":"string","required":true}, + "tooltip": {"ref":"QuestionTooltip","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationConfig": { + "dataType": "refObject", + "properties": { + "questions": {"dataType":"array","array":{"dataType":"refObject","ref":"Question"}}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RouteAuthRule": { + "dataType": "refObject", + "properties": { + "adminOnly": {"dataType":"boolean"}, + "loginRequired": {"dataType":"boolean"}, + "pattern": {"dataType":"string"}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UIRouteAuth": { + "dataType": "refObject", + "properties": { + "enabled": {"dataType":"boolean"}, + "rules": {"dataType":"array","array":{"dataType":"refObject","ref":"RouteAuthRule"}}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthResources": { + "dataType": "refObject", + "properties": { + "login": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, + "profile": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["get"],"required":true}},"required":true}, + "logout": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthConfigResponse": { + "dataType": "refObject", + "properties": { + "usernamePasswordMethod": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "otherMethods": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LoginResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"enum","enums":["success"],"required":true}, + "user": {"ref":"PublicUser","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Express.User": { + "dataType": "refObject", + "properties": { + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LogoutResponse": { + "dataType": "refObject", + "properties": { + "isAuth": {"dataType":"boolean","required":true}, + "user": {"dataType":"union","subSchemas":[{"ref":"Express.User"},{"dataType":"undefined"}],"required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GitAccountBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string"}, + "id": {"dataType":"string"}, + "gitAccount": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateUserResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + "username": {"dataType":"string","required":true}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateUserBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, + "password": {"dataType":"string","required":true}, + "email": {"dataType":"string","required":true}, + "gitAccount": {"dataType":"string","required":true}, + "admin": {"dataType":"boolean"}, + }, + "additionalProperties": true, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"ignore","bodyCoercion":true}); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + + +export function RegisterRoutes(app: Router) { + + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + + + + const argsUserController_getUsers: Record = { + }; + app.get('/api/v1/user', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.getUsers)), + + async function UserController_getUsers(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUsers, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getUsers', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_getUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/user/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.getUser)), + + async function UserController_getUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUser, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepos: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/api/v1/repo', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.getRepos)), + + async function RepoController_getRepos(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepos, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepos', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepo: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/repo/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.getRepo)), + + async function RepoController_getRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_createRepo: Record = { + body: {"in":"body","name":"body","required":true,"ref":"CreateRepoBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + conflictResponse: {"in":"res","name":"409","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/repo', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.createRepo)), + + async function RepoController_createRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_createRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'createRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addPushUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.patch('/api/v1/repo/:id/user/push', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.addPushUser)), + + async function RepoController_addPushUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addPushUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addPushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addAuthoriseUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.patch('/api/v1/repo/:id/user/authorise', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.addAuthoriseUser)), + + async function RepoController_addAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addAuthoriseUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removeAuthoriseUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/user/authorise/:username', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.removeAuthoriseUser)), + + async function RepoController_removeAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removeAuthoriseUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removeAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removePushUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/user/push/:username', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.removePushUser)), + + async function RepoController_removePushUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removePushUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removePushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_deleteRepo: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/delete', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.deleteRepo)), + + async function RepoController_deleteRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_deleteRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'deleteRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPushes: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/api/v1/push', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.getPushes)), + + async function PushController_getPushes(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPushes, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPushes', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/push/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.getPush)), + + async function PushController_getPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_rejectPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"RejectBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/reject', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.rejectPush)), + + async function PushController_rejectPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_rejectPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'rejectPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_authorisePush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"AuthoriseBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/authorise', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.authorisePush)), + + async function PushController_authorisePush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_authorisePush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'authorisePush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_cancelPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/cancel', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.cancelPush)), + + async function PushController_cancelPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_cancelPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'cancelPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHomeController_getResources: Record = { + }; + app.get('/api', + ...(fetchMiddlewares(HomeController)), + ...(fetchMiddlewares(HomeController.prototype.getResources)), + + async function HomeController_getResources(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsHomeController_getResources, request, response }); + + const controller = new HomeController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHealthController_check: Record = { + }; + app.get('/api/v1/healthcheck', + ...(fetchMiddlewares(HealthController)), + ...(fetchMiddlewares(HealthController.prototype.check)), + + async function HealthController_check(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsHealthController_check, request, response }); + + const controller = new HealthController(); + + await templateService.apiHandler({ + methodName: 'check', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getAttestation: Record = { + }; + app.get('/api/v1/config/attestation', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getAttestation)), + + async function ConfigController_getAttestation(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getAttestation, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getAttestation', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUrlShortener: Record = { + }; + app.get('/api/v1/config/urlShortener', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getUrlShortener)), + + async function ConfigController_getUrlShortener(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUrlShortener, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUrlShortener', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getContactEmail: Record = { + }; + app.get('/api/v1/config/contactEmail', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getContactEmail)), + + async function ConfigController_getContactEmail(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getContactEmail, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getContactEmail', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUiRouteAuth: Record = { + }; + app.get('/api/v1/config/uiRouteAuth', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getUiRouteAuth)), + + async function ConfigController_getUiRouteAuth(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUiRouteAuth, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUiRouteAuth', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getResources: Record = { + }; + app.get('/api/auth', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getResources)), + + async function AuthController_getResources(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getResources, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getAuthConfig: Record = { + }; + app.get('/api/auth/config', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getAuthConfig)), + + async function AuthController_getAuthConfig(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getAuthConfig, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getAuthConfig', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_login: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.post('/api/auth/login', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.login)), + + async function AuthController_login(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_login, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'login', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_initiateOIDC: Record = { + }; + app.get('/api/auth/openidconnect', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.initiateOIDC)), + + async function AuthController_initiateOIDC(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_initiateOIDC, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'initiateOIDC', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_handleOIDCCallback: Record = { + }; + app.get('/api/auth/openidconnect/callback', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.handleOIDCCallback)), + + async function AuthController_handleOIDCCallback(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_handleOIDCCallback, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'handleOIDCCallback', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_logout: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.post('/api/auth/logout', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.logout)), + + async function AuthController_logout(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_logout, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'logout', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getProfile: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/auth/profile', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getProfile)), + + async function AuthController_getProfile(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getProfile, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getProfile', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_updateGitAccount: Record = { + body: {"in":"body","name":"body","required":true,"ref":"GitAccountBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/auth/gitAccount', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.updateGitAccount)), + + async function AuthController_updateGitAccount(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_updateGitAccount, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'updateGitAccount', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_createUser: Record = { + body: {"in":"body","name":"body","required":true,"ref":"CreateUserBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/auth/create-user', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.createUser)), + + async function AuthController_createUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_createUser, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'createUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return async function runAuthenticationMiddleware(request: any, response: any, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // keep track of failed auth attempts so we can hand back the most + // recent one. This behavior was previously existing so preserving it + // here + const failedAttempts: any[] = []; + const pushAndRethrow = (error: any) => { + failedAttempts.push(error); + throw error; + }; + + const secMethodOrPromises: Promise[] = []; + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + const secMethodAndPromises: Promise[] = []; + + for (const name in secMethod) { + secMethodAndPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response) + .catch(pushAndRethrow) + ); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + secMethodOrPromises.push(Promise.all(secMethodAndPromises) + .then(users => { return users[0]; })); + } else { + for (const name in secMethod) { + secMethodOrPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response) + .catch(pushAndRethrow) + ); + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + try { + request['user'] = await Promise.any(secMethodOrPromises); + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; + } + + next(); + } + catch(err) { + // Show most recent error as response + const error = failedAttempts.pop(); + error.status = error.status || 401; + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; + } + next(error); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa From f206b8106c311040436be04865437deb8b0ac613 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 18 May 2026 04:03:29 +0200 Subject: [PATCH 07/18] fix(test): add mocks for tsoa passport --- test/service.tls.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/service.tls.test.ts b/test/service.tls.test.ts index dd0e163f9..5ef8bff43 100644 --- a/test/service.tls.test.ts +++ b/test/service.tls.test.ts @@ -87,6 +87,14 @@ describe('Service Module TLS', () => { initialize: vi.fn().mockReturnValue((_req: any, _res: any, next: any) => next()), session: vi.fn().mockReturnValue((_req: any, _res: any, next: any) => next()), }), + getPassport: vi.fn().mockReturnValue({ + authenticate: vi.fn().mockReturnValue((_req: any, _res: any, next: any) => next()), + }), + authStrategies: { + local: { type: 'local' }, + activedirectory: { type: 'activedirectory' }, + openidconnect: { type: 'openidconnect' }, + }, })); vi.doMock('../src/service/routes', () => ({ From a23b7ebfd02f716c5c29fb964aa4c179dedb6509 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 18 May 2026 17:24:04 +0200 Subject: [PATCH 08/18] fix: change behaviour on additional properties from ignore to throw --- src/service/generatedRoutes.ts | 52 ++++++++++++----------- src/service/interfaces/repo.interfaces.ts | 2 + test/testProxyRoute.test.ts | 22 ++++++++-- test/testPush.test.ts | 28 ------------ test/testRepoApi.test.ts | 45 ++++++++++++++------ tsoa.json | 2 +- 6 files changed, 81 insertions(+), 70 deletions(-) diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts index 6f3708058..b92f2cabb 100644 --- a/src/service/generatedRoutes.ts +++ b/src/service/generatedRoutes.ts @@ -37,7 +37,7 @@ const models: TsoaRoute.Models = { "gitAccount": {"dataType":"string","required":true}, "admin": {"dataType":"boolean","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "RepoWithProxy": { @@ -50,7 +50,7 @@ const models: TsoaRoute.Models = { "_id": {"dataType":"string"}, "proxyURL": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "MessageResponse": { @@ -58,7 +58,7 @@ const models: TsoaRoute.Models = { "properties": { "message": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "CreateRepoBody": { @@ -67,8 +67,10 @@ const models: TsoaRoute.Models = { "url": {"dataType":"string","required":true}, "name": {"dataType":"string","required":true}, "project": {"dataType":"string","required":true}, + "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, + "_id": {"dataType":"string"}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "UsernameBody": { @@ -76,7 +78,7 @@ const models: TsoaRoute.Models = { "properties": { "username": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Step": { @@ -91,7 +93,7 @@ const models: TsoaRoute.Models = { "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, "logs": {"dataType":"array","array":{"dataType":"string"},"default":[]}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "CommitData": { @@ -110,7 +112,7 @@ const models: TsoaRoute.Models = { "label": {"dataType":"string","required":true}, "checked": {"dataType":"boolean","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "CompletedAttestation": { @@ -159,7 +161,7 @@ const models: TsoaRoute.Models = { "proxyGitPath": {"dataType":"string"}, "newIdxFiles": {"dataType":"array","array":{"dataType":"string"}}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "RejectBody": { @@ -167,7 +169,7 @@ const models: TsoaRoute.Models = { "properties": { "reason": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AuthoriseBody": { @@ -175,7 +177,7 @@ const models: TsoaRoute.Models = { "properties": { "params": {"dataType":"nestedObjectLiteral","nestedProperties":{"attestation":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}},"required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ApiResources": { @@ -185,7 +187,7 @@ const models: TsoaRoute.Models = { "push": {"dataType":"string","required":true}, "auth": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "HealthResponse": { @@ -193,7 +195,7 @@ const models: TsoaRoute.Models = { "properties": { "message": {"dataType":"enum","enums":["ok"],"required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Link": { @@ -202,7 +204,7 @@ const models: TsoaRoute.Models = { "text": {"dataType":"string","required":true}, "url": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "QuestionTooltip": { @@ -211,7 +213,7 @@ const models: TsoaRoute.Models = { "links": {"dataType":"array","array":{"dataType":"refObject","ref":"Link"}}, "text": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Question": { @@ -220,7 +222,7 @@ const models: TsoaRoute.Models = { "label": {"dataType":"string","required":true}, "tooltip": {"ref":"QuestionTooltip","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AttestationConfig": { @@ -228,7 +230,7 @@ const models: TsoaRoute.Models = { "properties": { "questions": {"dataType":"array","array":{"dataType":"refObject","ref":"Question"}}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "RouteAuthRule": { @@ -257,7 +259,7 @@ const models: TsoaRoute.Models = { "profile": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["get"],"required":true}},"required":true}, "logout": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AuthConfigResponse": { @@ -266,7 +268,7 @@ const models: TsoaRoute.Models = { "usernamePasswordMethod": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, "otherMethods": {"dataType":"array","array":{"dataType":"string"},"required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "LoginResponse": { @@ -275,14 +277,14 @@ const models: TsoaRoute.Models = { "message": {"dataType":"enum","enums":["success"],"required":true}, "user": {"ref":"PublicUser","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Express.User": { "dataType": "refObject", "properties": { }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "LogoutResponse": { @@ -291,7 +293,7 @@ const models: TsoaRoute.Models = { "isAuth": {"dataType":"boolean","required":true}, "user": {"dataType":"union","subSchemas":[{"ref":"Express.User"},{"dataType":"undefined"}],"required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "GitAccountBody": { @@ -301,7 +303,7 @@ const models: TsoaRoute.Models = { "id": {"dataType":"string"}, "gitAccount": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "CreateUserResponse": { @@ -310,7 +312,7 @@ const models: TsoaRoute.Models = { "message": {"dataType":"string","required":true}, "username": {"dataType":"string","required":true}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "CreateUserBody": { @@ -322,11 +324,11 @@ const models: TsoaRoute.Models = { "gitAccount": {"dataType":"string","required":true}, "admin": {"dataType":"boolean"}, }, - "additionalProperties": true, + "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa }; -const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"ignore","bodyCoercion":true}); +const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/service/interfaces/repo.interfaces.ts b/src/service/interfaces/repo.interfaces.ts index 4a6238a11..ea0eab13d 100644 --- a/src/service/interfaces/repo.interfaces.ts +++ b/src/service/interfaces/repo.interfaces.ts @@ -24,6 +24,8 @@ export interface CreateRepoBody { url: string; name: string; project: string; + users: { canPush: string[]; canAuthorise: string[] }; + _id?: string; } export interface RepoWithProxy extends Repo { diff --git a/test/testProxyRoute.test.ts b/test/testProxyRoute.test.ts index 32f720015..63360e78f 100644 --- a/test/testProxyRoute.test.ts +++ b/test/testProxyRoute.test.ts @@ -58,6 +58,14 @@ const TEST_UNKNOWN_REPO = { fallbackUrlPrefix: '/finos/fdc3.git', }; +// `host` / `proxyUrlPrefix` / `fallbackUrlPrefix` are test-only helpers; the +// API rejects them as unknown properties on CreateRepoBody. +const toCreateBody = ({ url, name, project }: { url: string; name: string; project: string }) => ({ + url, + name, + project, +}); + afterAll(() => { vi.resetModules(); }); @@ -105,7 +113,7 @@ describe('proxy express application', () => { const res2 = await request(apiApp) .post('/api/v1/repo') .set('Cookie', cookie) - .send(TEST_DEFAULT_REPO); + .send(toCreateBody(TEST_DEFAULT_REPO)); expect(res2.status).toBe(200); } }); @@ -122,7 +130,10 @@ describe('proxy express application', () => { // Ensure default repo exists const repo = await db.getRepoByUrl(TEST_DEFAULT_REPO.url); if (!repo) { - await request(apiApp).post('/api/v1/repo').set('Cookie', cookie).send(TEST_DEFAULT_REPO); + await request(apiApp) + .post('/api/v1/repo') + .set('Cookie', cookie) + .send(toCreateBody(TEST_DEFAULT_REPO)); } // proxy a fetch request @@ -160,7 +171,7 @@ describe('proxy express application', () => { const res = await request(apiApp) .post('/api/v1/repo') .set('Cookie', cookie) - .send(TEST_GITLAB_REPO); + .send(toCreateBody(TEST_GITLAB_REPO)); expect(res.status).toBe(200); // confirm that the repo was created in the DB @@ -188,7 +199,10 @@ describe('proxy express application', () => { // Ensure the gitlab test repo exists (create it if a previous test didn't) let repo = await db.getRepoByUrl(TEST_GITLAB_REPO.url); if (!repo) { - await request(apiApp).post('/api/v1/repo').set('Cookie', cookie).send(TEST_GITLAB_REPO); + await request(apiApp) + .post('/api/v1/repo') + .set('Cookie', cookie) + .send(toCreateBody(TEST_GITLAB_REPO)); repo = await db.getRepoByUrl(TEST_GITLAB_REPO.url); } expect(repo).not.toBeNull(); diff --git a/test/testPush.test.ts b/test/testPush.test.ts index 307a57727..622d4dc19 100644 --- a/test/testPush.test.ts +++ b/test/testPush.test.ts @@ -162,10 +162,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], @@ -190,10 +186,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: false, }, ], @@ -219,10 +211,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], @@ -251,10 +239,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], @@ -276,10 +260,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], @@ -408,10 +388,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], @@ -433,10 +409,6 @@ describe('Push API', () => { attestation: [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { - text: 'Are you happy for this contribution to be pushed upstream?', - links: [], - }, checked: true, }, ], diff --git a/test/testRepoApi.test.ts b/test/testRepoApi.test.ts index cd9eb8487..7d0d4c60b 100644 --- a/test/testRepoApi.test.ts +++ b/test/testRepoApi.test.ts @@ -44,6 +44,13 @@ const TEST_REPO_NAKED = { host: '123.456.789:80', }; +// `host` is only kept on the fixtures above as an expected value for +// getAllProxiedHosts() assertions; the API rejects it as an unknown property. +const toCreateBody = (repo: T): Omit => { + const { host: _host, ...rest } = repo; + return rest; +}; + const cleanupRepo = async (url: string) => { const repo = await db.getRepoByUrl(url); if (repo) { @@ -77,7 +84,10 @@ describe('add new repo', () => { const ensureTestRepoExists = async () => { let repo = await db.getRepoByUrl(TEST_REPO.url); if (!repo) { - await request(app).post('/api/v1/repo').set('Cookie', `${cookie}`).send(TEST_REPO); + await request(app) + .post('/api/v1/repo') + .set('Cookie', `${cookie}`) + .send(toCreateBody(TEST_REPO)); repo = await db.getRepoByUrl(TEST_REPO.url); } if (repo) repoIds[0] = repo._id!; @@ -124,7 +134,10 @@ describe('add new repo', () => { // Ensure repo doesn't exist await cleanupRepo(TEST_REPO.url); - const res = await request(app).post('/api/v1/repo').set('Cookie', `${cookie}`).send(TEST_REPO); + const res = await request(app) + .post('/api/v1/repo') + .set('Cookie', `${cookie}`) + .send(toCreateBody(TEST_REPO)); expect(res.status).toBe(200); const repo = await fetchRepoOrThrow(TEST_REPO.url); @@ -155,7 +168,10 @@ describe('add new repo', () => { it('return a 409 error if the repo already exists', async () => { await ensureTestRepoExists(); - const res = await request(app).post('/api/v1/repo').set('Cookie', `${cookie}`).send(TEST_REPO); + const res = await request(app) + .post('/api/v1/repo') + .set('Cookie', `${cookie}`) + .send(toCreateBody(TEST_REPO)); expect(res.status).toBe(409); expect(res.body.message).toBe('Repository ' + TEST_REPO.url + ' already exists!'); }); @@ -349,7 +365,7 @@ describe('add new repo', () => { const res = await request(app) .post('/api/v1/repo') .set('Cookie', cookie) - .send(TEST_REPO_NON_GITHUB); + .send(toCreateBody(TEST_REPO_NON_GITHUB)); expect(res.status).toBe(200); const repo = await fetchRepoOrThrow(TEST_REPO_NON_GITHUB.url); @@ -367,7 +383,7 @@ describe('add new repo', () => { const res2 = await request(app) .post('/api/v1/repo') .set('Cookie', cookie) - .send(TEST_REPO_NAKED); + .send(toCreateBody(TEST_REPO_NAKED)); expect(res2.status).toBe(200); const repo2 = await fetchRepoOrThrow(TEST_REPO_NAKED.url); @@ -383,14 +399,20 @@ describe('add new repo', () => { // Ensure repos exist before deleting let repo1 = await db.getRepoByUrl(TEST_REPO_NON_GITHUB.url); if (!repo1) { - await request(app).post('/api/v1/repo').set('Cookie', cookie).send(TEST_REPO_NON_GITHUB); + await request(app) + .post('/api/v1/repo') + .set('Cookie', cookie) + .send(toCreateBody(TEST_REPO_NON_GITHUB)); repo1 = await db.getRepoByUrl(TEST_REPO_NON_GITHUB.url); } repoIds[1] = repo1!._id!; let repo2 = await db.getRepoByUrl(TEST_REPO_NAKED.url); if (!repo2) { - await request(app).post('/api/v1/repo').set('Cookie', cookie).send(TEST_REPO_NAKED); + await request(app) + .post('/api/v1/repo') + .set('Cookie', cookie) + .send(toCreateBody(TEST_REPO_NAKED)); repo2 = await db.getRepoByUrl(TEST_REPO_NAKED.url); } repoIds[2] = repo2!._id!; @@ -466,7 +488,10 @@ describe('repo routes - edge cases', () => { setCookie(nonAdminRes, 'nonAdmin'); // Create a test repo - await request(app).post('/api/v1/repo').set('Cookie', adminCookie).send(TEST_REPO); + await request(app) + .post('/api/v1/repo') + .set('Cookie', adminCookie) + .send(toCreateBody(TEST_REPO)); const repo = await fetchRepoOrThrow(TEST_REPO.url); repoId = repo._id!; @@ -477,7 +502,6 @@ describe('repo routes - edge cases', () => { url: 'https://github.com/test/unauthorized-repo.git', name: 'unauthorized-repo', project: 'test', - host: 'github.com', }); expect(res.status).toBe(401); @@ -489,7 +513,6 @@ describe('repo routes - edge cases', () => { url: 'https://github.com/test/unauthenticated-repo.git', name: 'unauthenticated-repo', project: 'test', - host: 'github.com', }); expect(res.status).toBe(401); @@ -500,7 +523,6 @@ describe('repo routes - edge cases', () => { const res = await request(app).post('/api/v1/repo').set('Cookie', adminCookie).send({ name: 'no-url-repo', project: 'test', - host: 'github.com', }); expect(res.status).toBe(400); @@ -515,7 +537,6 @@ describe('repo routes - edge cases', () => { url: '', name: 'invalid-repo', project: 'test', - host: 'github.com', }); expect(res.status).toBe(400); expect(res.body.message).toBe('Repository url is required'); diff --git a/tsoa.json b/tsoa.json index 8000b462f..b58265054 100644 --- a/tsoa.json +++ b/tsoa.json @@ -1,6 +1,6 @@ { "entryFile": "src/app.ts", - "noImplicitAdditionalProperties": "ignore", + "noImplicitAdditionalProperties": "throw-on-extras", "controllerPathGlobs": ["src/service/controllers/**/*Controller.ts"], "spec": { "outputDirectory": "dist", From a1650eb7c80490f434ad581ac8c2bfb666e247d5 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 18 May 2026 17:40:15 +0200 Subject: [PATCH 09/18] fix: add generatedRoutes.ts to ignore for prettier --- .prettierignore | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..5eedb4a47 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/service/generatedRoutes.ts diff --git a/package.json b/package.json index 12dc311a1..8bc4e19c8 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "prepare": "node ./scripts/prepare.js", "lint": "eslint", "lint:fix": "eslint --fix", - "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --config ./.prettierrc", - "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --config ./.prettierrc", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --config ./.prettierrc", + "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --config ./.prettierrc", "gen-schema-doc": "node ./scripts/doc-schema.js", "cypress:run": "cypress run", "cypress:run:docker": "cypress run --config specPattern='cypress/e2e/docker/**/*.cy.{js,ts}'", From 180c8ce27f836aeef31d87b690b8c6ce4ec0addd Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 1 Jun 2026 08:28:10 +0200 Subject: [PATCH 10/18] fix(service): fix interface for repo creation by making users object optional --- src/service/generatedRoutes.ts | 2 +- src/service/interfaces/repo.interfaces.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts index b92f2cabb..588635686 100644 --- a/src/service/generatedRoutes.ts +++ b/src/service/generatedRoutes.ts @@ -67,7 +67,7 @@ const models: TsoaRoute.Models = { "url": {"dataType":"string","required":true}, "name": {"dataType":"string","required":true}, "project": {"dataType":"string","required":true}, - "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, + "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}}}, "_id": {"dataType":"string"}, }, "additionalProperties": false, diff --git a/src/service/interfaces/repo.interfaces.ts b/src/service/interfaces/repo.interfaces.ts index ea0eab13d..df2177a9d 100644 --- a/src/service/interfaces/repo.interfaces.ts +++ b/src/service/interfaces/repo.interfaces.ts @@ -24,7 +24,7 @@ export interface CreateRepoBody { url: string; name: string; project: string; - users: { canPush: string[]; canAuthorise: string[] }; + users?: { canPush: string[]; canAuthorise: string[] }; _id?: string; } From 34030424c8e7a646609b6d8cd3deede237516100 Mon Sep 17 00:00:00 2001 From: Fabio Vincenzi <93596376+fabiovincenzi@users.noreply.github.com> Date: Thu, 4 Jun 2026 05:47:06 +0200 Subject: [PATCH 11/18] feat: generate API reference docs from OpenAPI spec * feat: generate API reference docs from OpenAPI spec * fix: escape backslashes before pipes in Markdown table descriptions --- package.json | 1 + scripts/doc-swagger.js | 362 ++++++++++++++++++++++++++++++ website/docs/api/auth.mdx | 127 +++++++++++ website/docs/api/config.mdx | 40 ++++ website/docs/api/health.mdx | 16 ++ website/docs/api/home.mdx | 16 ++ website/docs/api/index.mdx | 32 +++ website/docs/api/push.mdx | 126 +++++++++++ website/docs/api/repositories.mdx | 192 ++++++++++++++++ website/docs/api/users.mdx | 43 ++++ website/sidebars.js | 20 ++ 11 files changed, 975 insertions(+) create mode 100644 scripts/doc-swagger.js create mode 100644 website/docs/api/auth.mdx create mode 100644 website/docs/api/config.mdx create mode 100644 website/docs/api/health.mdx create mode 100644 website/docs/api/home.mdx create mode 100644 website/docs/api/index.mdx create mode 100644 website/docs/api/push.mdx create mode 100644 website/docs/api/repositories.mdx create mode 100644 website/docs/api/users.mdx diff --git a/package.json b/package.json index 671637879..e8dd85ee9 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --config ./.prettierrc", "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --config ./.prettierrc", "gen-schema-doc": "node ./scripts/doc-schema.js", + "gen-swagger-doc": "node ./scripts/doc-swagger.js", "cypress:run": "cypress run", "cypress:run:docker": "cypress run --config specPattern='cypress/e2e/docker/**/*.cy.{js,ts}'", "cypress:open": "cypress open", diff --git a/scripts/doc-swagger.js b/scripts/doc-swagger.js new file mode 100644 index 000000000..c5f08259e --- /dev/null +++ b/scripts/doc-swagger.js @@ -0,0 +1,362 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { readFileSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); + +const SWAGGER_FILE = './dist/swagger.json'; +const OUTPUT_DIR = './website/docs/api'; + +const METHOD_COLORS = { + get: '#61affe', + post: '#49cc90', + put: '#fca130', + patch: '#50e3c2', + delete: '#f93e3e', +}; + +const STATUS_DESCRIPTIONS = { + 200: 'OK', + 201: 'Created', + 204: 'No Content', + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 409: 'Conflict', + 500: 'Internal Server Error', +}; + +function loadSpec() { + const raw = readFileSync(SWAGGER_FILE, 'utf-8'); + return JSON.parse(raw); +} + +function httpMethodOrder(method) { + const order = { get: 0, post: 1, put: 2, patch: 3, delete: 4 }; + return order[method] ?? 5; +} + +function resolveRef(ref, spec) { + const path = ref.replace('#/', '').split('/'); + let current = spec; + for (const segment of path) { + current = current[segment]; + if (!current) return null; + } + return current; +} + +function resolveSchema(schema, spec) { + if (!schema) return null; + if (schema.$ref) return resolveRef(schema.$ref, spec); + return schema; +} + +function formatType(schema, spec) { + if (!schema) return '`any`'; + const resolved = resolveSchema(schema, spec); + if (!resolved) return '`any`'; + + if (resolved.type === 'array') { + const itemType = resolved.items ? formatType(resolved.items, spec) : '`any`'; + return `${itemType}[]`; + } + + if (schema.$ref) { + const name = schema.$ref.split('/').pop(); + return `\`${name}\``; + } + + if (resolved.enum) { + return resolved.enum.map((v) => `\`"${v}"\``).join(' \\| '); + } + + return `\`${resolved.type || 'object'}\``; +} + +function methodBadge(method) { + const color = METHOD_COLORS[method] || '#999'; + const label = method.toUpperCase(); + return `${label}`; +} + +function authBadge() { + return `AUTH`; +} + +function statusBadge(code) { + let color = '#49cc90'; + if (code >= 400 && code < 500) color = '#fca130'; + if (code >= 500) color = '#f93e3e'; + if (code >= 300 && code < 400) color = '#61affe'; + return `${code}`; +} + +function renderSchemaProperties(schema, spec) { + const resolved = resolveSchema(schema, spec); + if (!resolved || !resolved.properties) return ''; + + const required = new Set(resolved.required || []); + let lines = []; + + lines.push('| Field | Type | Required | Description |'); + lines.push('|-------|------|:--------:|-------------|'); + + for (const [name, prop] of Object.entries(resolved.properties)) { + const propResolved = resolveSchema(prop, spec); + const type = formatType(prop, spec); + const isRequired = required.has(name) ? '**Yes**' : 'No'; + const desc = (propResolved?.description || '').replace(/\\/g, '\\\\').replace(/\|/g, '\\|'); + lines.push(`| \`${name}\` | ${type} | ${isRequired} | ${desc} |`); + } + + return lines.join('\n'); +} + +function renderParameters(parameters, spec) { + if (!parameters || parameters.length === 0) return ''; + + const params = parameters.map((p) => (p.$ref ? resolveRef(p.$ref, spec) : p)).filter(Boolean); + if (params.length === 0) return ''; + + let lines = []; + lines.push('#### Parameters\n'); + lines.push('| Name | In | Type | Required | Description |'); + lines.push('|------|:---:|------|:--------:|-------------|'); + + for (const param of params) { + const type = formatType(param.schema, spec); + const required = param.required ? '**Yes**' : 'No'; + const desc = (param.description || '').replace(/\\/g, '\\\\').replace(/\|/g, '\\|'); + const inBadge = param.in === 'path' ? '`path`' : '`query`'; + lines.push(`| \`${param.name}\` | ${inBadge} | ${type} | ${required} | ${desc} |`); + } + + return lines.join('\n'); +} + +function renderRequestBody(requestBody, spec) { + if (!requestBody) return ''; + + const content = requestBody.content; + if (!content) return ''; + + const jsonContent = content['application/json']; + if (!jsonContent || !jsonContent.schema) return ''; + + const schema = resolveSchema(jsonContent.schema, spec); + if (!schema) return ''; + + let lines = ['#### Request Body\n']; + const table = renderSchemaProperties(jsonContent.schema, spec); + if (table) { + lines.push(table); + } else { + lines.push(`Type: ${formatType(jsonContent.schema, spec)}`); + } + return lines.join('\n'); +} + +function renderResponses(responses) { + if (!responses) return ''; + + let lines = ['#### Responses\n']; + + for (const [status, response] of Object.entries(responses)) { + const code = parseInt(status, 10); + const badge = statusBadge(code); + const desc = response.description || STATUS_DESCRIPTIONS[code] || ''; + lines.push(`- ${badge} ${desc}`); + } + + return lines.join('\n'); +} + +function renderSecurityNote(security) { + if (!security || security.length === 0) return ''; + return ':::info[Authorization Required]\nThis endpoint requires a valid **JWT Bearer token** in the `Authorization` header.\n:::'; +} + +function countEndpoints(operations) { + const methods = {}; + for (const op of operations) { + const m = op.method.toUpperCase(); + methods[m] = (methods[m] || 0) + 1; + } + return Object.entries(methods) + .map(([m, c]) => `${c} ${m}`) + .join(', '); +} + +function generateTagPage(tag, operations, spec) { + let lines = []; + + lines.push('---'); + lines.push(`title: ${tag}`); + lines.push(`description: ${tag} API endpoints for GitProxy`); + lines.push('---'); + lines.push(''); + lines.push(`# ${tag}\n`); + + const tagDesc = (spec.tags || []).find((t) => t.name === tag); + if (tagDesc && tagDesc.description) { + lines.push(`${tagDesc.description}\n`); + } + + lines.push( + `*${operations.length} endpoint${operations.length !== 1 ? 's' : ''} — ${countEndpoints(operations)}*\n`, + ); + + operations.sort((a, b) => { + const methodDiff = httpMethodOrder(a.method) - httpMethodOrder(b.method); + if (methodDiff !== 0) return methodDiff; + return a.path.localeCompare(b.path); + }); + + for (const op of operations) { + const badge = methodBadge(op.method); + const auth = op.security && op.security.length > 0 ? authBadge() : ''; + + lines.push(`## ${badge} \`${op.path}\`${auth}\n`); + + if (op.description) { + lines.push(`${op.description}\n`); + } else if (op.summary) { + lines.push(`${op.summary}\n`); + } + + const secNote = renderSecurityNote(op.security); + if (secNote) { + lines.push(`${secNote}\n`); + } + + const params = renderParameters(op.parameters, spec); + if (params) { + lines.push(`${params}\n`); + } + + const body = renderRequestBody(op.requestBody, spec); + if (body) { + lines.push(`${body}\n`); + } + + const responses = renderResponses(op.responses); + if (responses) { + lines.push(`${responses}\n`); + } + + lines.push('---\n'); + } + + return lines.join('\n'); +} + +function generateIndexPage(spec, tagList, tagOperations) { + const info = spec.info || {}; + let lines = []; + + lines.push('---'); + lines.push('title: API Reference'); + lines.push('description: REST API reference documentation for GitProxy'); + lines.push('---'); + lines.push(''); + lines.push('# API Reference\n'); + + if (info.description) { + lines.push(`${info.description}\n`); + } + + lines.push(`:::info[API Version]\n**${info.version || 'unknown'}** — OpenAPI 3.0\n:::\n`); + + const securitySchemes = spec.components?.securitySchemes; + if (securitySchemes) { + lines.push('## Authentication\n'); + for (const [name, scheme] of Object.entries(securitySchemes)) { + if (scheme.type === 'http') { + lines.push( + `Most endpoints require a **${scheme.scheme.toUpperCase()}** token${scheme.bearerFormat ? ` (${scheme.bearerFormat})` : ''} passed via the \`Authorization\` header:\n`, + ); + lines.push('```\nAuthorization: Bearer \n```\n'); + } else { + lines.push(`- **${name}**: ${scheme.type}\n`); + } + } + } + + lines.push('## Endpoints\n'); + + const totalEndpoints = Object.values(tagOperations).reduce((sum, ops) => sum + ops.length, 0); + lines.push(`${totalEndpoints} endpoints across ${tagList.length} groups:\n`); + + for (const tag of tagList) { + const slug = tag.toLowerCase().replace(/\s+/g, '-'); + const ops = tagOperations[tag]; + const summary = countEndpoints(ops); + lines.push(`- [**${tag}**](/docs/api/${slug}) — ${summary}`); + } + + lines.push(''); + return lines.join('\n'); +} + +function main() { + let spec; + try { + spec = loadSpec(); + } catch (err) { + console.error(`Failed to read ${SWAGGER_FILE}: ${err.message}`); + console.error('Run "npm run build-tsoa" first to generate the OpenAPI spec.'); + process.exit(1); + } + + const tagOperations = {}; + + for (const [path, methods] of Object.entries(spec.paths || {})) { + for (const [method, operation] of Object.entries(methods)) { + if (['parameters', 'summary', 'description'].includes(method)) continue; + + const tags = operation.tags || ['Default']; + for (const tag of tags) { + if (!tagOperations[tag]) tagOperations[tag] = []; + tagOperations[tag].push({ + path, + method, + ...operation, + }); + } + } + } + + mkdirSync(OUTPUT_DIR, { recursive: true }); + + const tagList = Object.keys(tagOperations).sort(); + + const indexContent = generateIndexPage(spec, tagList, tagOperations); + writeFileSync(join(OUTPUT_DIR, 'index.mdx'), indexContent); + console.log(`Wrote ${join(OUTPUT_DIR, 'index.mdx')}`); + + for (const [tag, operations] of Object.entries(tagOperations)) { + const slug = tag.toLowerCase().replace(/\s+/g, '-'); + const content = generateTagPage(tag, operations, spec); + writeFileSync(join(OUTPUT_DIR, `${slug}.mdx`), content); + console.log(`Wrote ${join(OUTPUT_DIR, `${slug}.mdx`)}`); + } + + console.log(`\nGenerated API docs for ${tagList.length} tag(s): ${tagList.join(', ')}`); +} + +main(); diff --git a/website/docs/api/auth.mdx b/website/docs/api/auth.mdx new file mode 100644 index 000000000..baf238f2c --- /dev/null +++ b/website/docs/api/auth.mdx @@ -0,0 +1,127 @@ +--- +title: Auth +description: Auth API endpoints for GitProxy +--- + +# Auth + +*9 endpoints — 5 GET, 4 POST* + +## GET `/api/auth` + +Returns links to the available authentication resource endpoints. + +#### Responses + +- 200 Ok + +--- + +## GET `/api/auth/config` + +Returns the enabled authentication methods available to the UI. + +#### Responses + +- 200 Ok + +--- + +## GET `/api/auth/openidconnect` + +Initiates the OpenID Connect authentication flow (redirects to the OIDC provider). + +#### Responses + +- 204 No content + +--- + +## GET `/api/auth/openidconnect/callback` + +OpenID Connect callback — exchanges the authorization code for a session. + +#### Responses + +- 204 No content + +--- + +## GET `/api/auth/profile` + +Returns the profile of the currently authenticated user. + +#### Responses + +- 200 Ok +- 401 Unauthorized +- 404 Not Found + +--- + +## POST `/api/auth/create-user` + +Creates a new user. Requires admin privileges. + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `username` | `string` | **Yes** | | +| `password` | `string` | **Yes** | | +| `email` | `string` | **Yes** | | +| `gitAccount` | `string` | **Yes** | | +| `admin` | `boolean` | No | | + +#### Responses + +- 200 Ok +- 403 Forbidden +- 500 Internal Server Error + +--- + +## POST `/api/auth/gitAccount` + +Updates the Git account (username) of a user. +Admins may update any user; non-admins may only update their own account. + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `username` | `string` | No | | +| `id` | `string` | No | | +| `gitAccount` | `string` | **Yes** | | + +#### Responses + +- 204 No content +- 400 Bad Request +- 401 Unauthorized +- 403 Forbidden +- 404 Not Found +- 500 Internal Server Error + +--- + +## POST `/api/auth/login` + +Authenticates the user with a username/password strategy. +The appropriate passport strategy is selected dynamically based on configuration. + +#### Responses + +- 200 Ok + +--- + +## POST `/api/auth/logout` + +Logs out the current user and clears the session cookie. + +#### Responses + +- 200 Ok + +--- diff --git a/website/docs/api/config.mdx b/website/docs/api/config.mdx new file mode 100644 index 000000000..b7df40d10 --- /dev/null +++ b/website/docs/api/config.mdx @@ -0,0 +1,40 @@ +--- +title: Config +description: Config API endpoints for GitProxy +--- + +# Config + +*4 endpoints — 4 GET* + +## GET `/api/v1/config/attestation` + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/config/contactEmail` + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/config/uiRouteAuth` + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/config/urlShortener` + +#### Responses + +- 200 Ok + +--- diff --git a/website/docs/api/health.mdx b/website/docs/api/health.mdx new file mode 100644 index 000000000..98c797433 --- /dev/null +++ b/website/docs/api/health.mdx @@ -0,0 +1,16 @@ +--- +title: Health +description: Health API endpoints for GitProxy +--- + +# Health + +*1 endpoint — 1 GET* + +## GET `/api/v1/healthcheck` + +#### Responses + +- 200 Ok + +--- diff --git a/website/docs/api/home.mdx b/website/docs/api/home.mdx new file mode 100644 index 000000000..098b3a49d --- /dev/null +++ b/website/docs/api/home.mdx @@ -0,0 +1,16 @@ +--- +title: Home +description: Home API endpoints for GitProxy +--- + +# Home + +*1 endpoint — 1 GET* + +## GET `/api` + +#### Responses + +- 200 Ok + +--- diff --git a/website/docs/api/index.mdx b/website/docs/api/index.mdx new file mode 100644 index 000000000..ec03d2784 --- /dev/null +++ b/website/docs/api/index.mdx @@ -0,0 +1,32 @@ +--- +title: API Reference +description: REST API reference documentation for GitProxy +--- + +# API Reference + +Deploy custom push protections and policies on top of Git. + +:::info[API Version] +**2.0.0-rc.6** — OpenAPI 3.0 +::: + +## Authentication + +Most endpoints require a **BEARER** token (JWT) passed via the `Authorization` header: + +``` +Authorization: Bearer +``` + +## Endpoints + +30 endpoints across 7 groups: + +- [**Auth**](/docs/api/auth) — 5 GET, 4 POST +- [**Config**](/docs/api/config) — 4 GET +- [**Health**](/docs/api/health) — 1 GET +- [**Home**](/docs/api/home) — 1 GET +- [**Push**](/docs/api/push) — 2 GET, 3 POST +- [**Repositories**](/docs/api/repositories) — 2 GET, 1 POST, 2 PATCH, 3 DELETE +- [**Users**](/docs/api/users) — 2 GET diff --git a/website/docs/api/push.mdx b/website/docs/api/push.mdx new file mode 100644 index 000000000..e5439d111 --- /dev/null +++ b/website/docs/api/push.mdx @@ -0,0 +1,126 @@ +--- +title: Push +description: Push API endpoints for GitProxy +--- + +# Push + +*5 endpoints — 2 GET, 3 POST* + +## GET `/api/v1/push`AUTH + +Returns push requests, optionally filtered by query parameters. +Supported filters: any field from PushQuery (error, blocked, allowPush, authorised, canceled, rejected, type). + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/push/{id}`AUTH + +Returns a single push request by ID. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 404 Not Found + +--- + +## POST `/api/v1/push/{id}/authorise`AUTH + +Authorises (approves) a pending push request. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `params` | `object` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized +- 403 Forbidden +- 404 Not Found + +--- + +## POST `/api/v1/push/{id}/cancel`AUTH + +Cancels a pending push request. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 401 Unauthorized +- 403 Forbidden + +--- + +## POST `/api/v1/push/{id}/reject`AUTH + +Rejects a pending push request. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `reason` | `string` | **Yes** | The reason for rejecting the push request. | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized +- 403 Forbidden +- 404 Not Found + +--- diff --git a/website/docs/api/repositories.mdx b/website/docs/api/repositories.mdx new file mode 100644 index 000000000..64a90be96 --- /dev/null +++ b/website/docs/api/repositories.mdx @@ -0,0 +1,192 @@ +--- +title: Repositories +description: Repositories API endpoints for GitProxy +--- + +# Repositories + +*8 endpoints — 2 GET, 1 POST, 2 PATCH, 3 DELETE* + +## GET `/api/v1/repo`AUTH + +Returns repositories, optionally filtered by query parameters. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/repo/{id}`AUTH + +Returns a single repository by ID. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 404 Not Found + +--- + +## POST `/api/v1/repo`AUTH + +Creates a new repository. May restart the proxy if a new origin is added. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `url` | `string` | **Yes** | | +| `name` | `string` | **Yes** | | +| `project` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized +- 409 Conflict +- 500 Internal Server Error + +--- + +## PATCH `/api/v1/repo/{id}/user/authorise`AUTH + +Grants a user authorise permission on a repository. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `username` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized + +--- + +## PATCH `/api/v1/repo/{id}/user/push`AUTH + +Grants a user push permission on a repository. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `username` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized + +--- + +## DELETE `/api/v1/repo/{id}/delete`AUTH + +Deletes a repository. May restart the proxy if a proxied host is removed. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 401 Unauthorized + +--- + +## DELETE `/api/v1/repo/{id}/user/authorise/{username}`AUTH + +Revokes a user's authorise permission on a repository. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | +| `username` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized + +--- + +## DELETE `/api/v1/repo/{id}/user/push/{username}`AUTH + +Revokes a user's push permission on a repository. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | +| `username` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized + +--- diff --git a/website/docs/api/users.mdx b/website/docs/api/users.mdx new file mode 100644 index 000000000..e1c25ceaf --- /dev/null +++ b/website/docs/api/users.mdx @@ -0,0 +1,43 @@ +--- +title: Users +description: Users API endpoints for GitProxy +--- + +# Users + +*2 endpoints — 2 GET* + +## GET `/api/v1/user`AUTH + +Returns all registered users (public fields only). + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Responses + +- 200 Ok + +--- + +## GET `/api/v1/user/{id}`AUTH + +Returns a single user by username. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `id` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 404 Not Found + +--- diff --git a/website/sidebars.js b/website/sidebars.js index aa9bb948a..ffbf16cbc 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -52,6 +52,25 @@ module.exports = { collapsed: false, items: ['configuration/overview', 'configuration/reference', 'configuration/pre-receive'], }, + { + type: 'category', + label: 'API Reference', + link: { + type: 'doc', + id: 'api/index', + }, + collapsible: true, + collapsed: false, + items: [ + 'api/auth', + 'api/config', + 'api/health', + 'api/home', + 'api/push', + 'api/repositories', + 'api/users', + ], + }, { type: 'category', label: 'Architecture', @@ -66,6 +85,7 @@ module.exports = { collapsed: false, items: ['architecture/architecture', 'architecture/processors'], }, + 'deployment', { type: 'category', label: 'Development', From 888808754b2afd0924b03ea140bb0644c2a84f20 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 5 Jun 2026 16:43:32 +0900 Subject: [PATCH 12/18] fix: remove tooltip in defaultQuestions array to fix push e2e test --- tests/e2e/push.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/push.test.ts b/tests/e2e/push.test.ts index a824af82a..9fbf6fdb0 100644 --- a/tests/e2e/push.test.ts +++ b/tests/e2e/push.test.ts @@ -674,7 +674,6 @@ describe('Git Proxy E2E - Repository Push Tests', () => { const defaultQuestions = [ { label: 'I am happy for this to be pushed to the upstream repository', - tooltip: { label: 'test' }, checked: 'true', }, ]; From 40479375a2d1aea324997635420e5f6569a222e6 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 5 Jun 2026 17:19:31 +0900 Subject: [PATCH 13/18] fix: replace RepoView with Repo and remove unused proxyUrl field --- src/ui/services/repo.ts | 2 +- src/ui/views/RepoList/Components/NewRepo.tsx | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ui/services/repo.ts b/src/ui/services/repo.ts index 909127c98..690df885b 100644 --- a/src/ui/services/repo.ts +++ b/src/ui/services/repo.ts @@ -76,7 +76,7 @@ const getRepo = async (id: string): Promise> => { } }; -const addRepo = async (repo: RepoView): Promise> => { +const addRepo = async (repo: Repo): Promise> => { const apiV1Base = await getApiV1BaseUrl(); const url = new URL(`${apiV1Base}/repo`); diff --git a/src/ui/views/RepoList/Components/NewRepo.tsx b/src/ui/views/RepoList/Components/NewRepo.tsx index 11aa714a6..ee1d9327d 100644 --- a/src/ui/views/RepoList/Components/NewRepo.tsx +++ b/src/ui/views/RepoList/Components/NewRepo.tsx @@ -31,16 +31,16 @@ import { addRepo } from '../../../services/repo'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { RepoIcon } from '@primer/octicons-react'; -import { RepoView } from '../../../types'; +import { Repo } from '../../../../db/types'; interface AddRepositoryDialogProps { open: boolean; onClose: () => void; - onSuccess: (repo: RepoView) => void; + onSuccess: (repo: Repo) => void; } interface NewRepoProps { - onSuccess: (repo: RepoView) => Promise; + onSuccess: (repo: Repo) => Promise; } const useStyles = makeStyles(styles as any); @@ -59,7 +59,7 @@ const AddRepositoryDialog: React.FC = ({ open, onClose onClose(); }; - const handleSuccess = (repo: RepoView) => { + const handleSuccess = (repo: Repo) => { onSuccess(repo); setTip(true); }; @@ -71,11 +71,10 @@ const AddRepositoryDialog: React.FC = ({ open, onClose }; const add = async () => { - const repo: RepoView = { + const repo: Repo = { project: project.trim(), name: name.trim(), url: url.trim(), - proxyURL: '', users: { canPush: [], canAuthorise: [] }, }; From b2d07a6b6dada6567ed7956015a1e6b6b7c5fd5a Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 5 Jun 2026 17:31:22 +0900 Subject: [PATCH 14/18] chore: npm run format --- src/service/generatedRoutes.ts | 3131 +++++++++++++++++++------------- 1 file changed, 1836 insertions(+), 1295 deletions(-) diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts index 588635686..9cb9caa4c 100644 --- a/src/service/generatedRoutes.ts +++ b/src/service/generatedRoutes.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import type { TsoaRoute } from '@tsoa/runtime'; -import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; +import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { UserController } from './controllers/UserController'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa @@ -21,1362 +21,1903 @@ import { expressAuthentication } from './authentication'; // @ts-ignore - no great way to install types from subpackage import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; -const expressAuthenticationRecasted = expressAuthentication as (req: ExRequest, securityName: string, scopes?: string[], res?: ExResponse) => Promise; - +const expressAuthenticationRecasted = expressAuthentication as ( + req: ExRequest, + securityName: string, + scopes?: string[], + res?: ExResponse, +) => Promise; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const models: TsoaRoute.Models = { - "PublicUser": { - "dataType": "refObject", - "properties": { - "username": {"dataType":"string","required":true}, - "displayName": {"dataType":"string","required":true}, - "email": {"dataType":"string","required":true}, - "title": {"dataType":"string","required":true}, - "gitAccount": {"dataType":"string","required":true}, - "admin": {"dataType":"boolean","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "RepoWithProxy": { - "dataType": "refObject", - "properties": { - "project": {"dataType":"string","required":true}, - "name": {"dataType":"string","required":true}, - "url": {"dataType":"string","required":true}, - "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, - "_id": {"dataType":"string"}, - "proxyURL": {"dataType":"string","required":true}, - }, - "additionalProperties": false, + PublicUser: { + dataType: 'refObject', + properties: { + username: { dataType: 'string', required: true }, + displayName: { dataType: 'string', required: true }, + email: { dataType: 'string', required: true }, + title: { dataType: 'string', required: true }, + gitAccount: { dataType: 'string', required: true }, + admin: { dataType: 'boolean', required: true }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "MessageResponse": { - "dataType": "refObject", - "properties": { - "message": {"dataType":"string","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CreateRepoBody": { - "dataType": "refObject", - "properties": { - "url": {"dataType":"string","required":true}, - "name": {"dataType":"string","required":true}, - "project": {"dataType":"string","required":true}, - "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}}}, - "_id": {"dataType":"string"}, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + RepoWithProxy: { + dataType: 'refObject', + properties: { + project: { dataType: 'string', required: true }, + name: { dataType: 'string', required: true }, + url: { dataType: 'string', required: true }, + users: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + canAuthorise: { dataType: 'array', array: { dataType: 'string' }, required: true }, + canPush: { dataType: 'array', array: { dataType: 'string' }, required: true }, }, - "additionalProperties": false, + required: true, + }, + _id: { dataType: 'string' }, + proxyURL: { dataType: 'string', required: true }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "UsernameBody": { - "dataType": "refObject", - "properties": { - "username": {"dataType":"string","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Step": { - "dataType": "refObject", - "properties": { - "id": {"dataType":"string","required":true}, - "stepName": {"dataType":"string","required":true}, - "content": {"dataType":"any","required":true}, - "error": {"dataType":"boolean","required":true}, - "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, - "blocked": {"dataType":"boolean","required":true}, - "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, - "logs": {"dataType":"array","array":{"dataType":"string"},"default":[]}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CommitData": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true},"commitTimestamp":{"dataType":"string","required":true},"committerEmail":{"dataType":"string","required":true},"authorEmail":{"dataType":"string","required":true},"committer":{"dataType":"string","required":true},"author":{"dataType":"string","required":true},"parent":{"dataType":"string","required":true},"tree":{"dataType":"string","required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AttestationBase": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"automated":{"dataType":"boolean"},"timestamp":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},"reviewer":{"dataType":"nestedObjectLiteral","nestedProperties":{"email":{"dataType":"string","required":true},"username":{"dataType":"string","required":true}},"required":true}},"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AttestationAnswer": { - "dataType": "refObject", - "properties": { - "label": {"dataType":"string","required":true}, - "checked": {"dataType":"boolean","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CompletedAttestation": { - "dataType": "refAlias", - "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"answers":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}}}],"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Rejection": { - "dataType": "refAlias", - "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string","required":true}}}],"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Action": { - "dataType": "refObject", - "properties": { - "id": {"dataType":"string","required":true}, - "type": {"dataType":"string","required":true}, - "method": {"dataType":"string","required":true}, - "timestamp": {"dataType":"double","required":true}, - "project": {"dataType":"string","required":true}, - "repoName": {"dataType":"string","required":true}, - "url": {"dataType":"string","required":true}, - "repo": {"dataType":"string","required":true}, - "steps": {"dataType":"array","array":{"dataType":"refObject","ref":"Step"},"default":[]}, - "error": {"dataType":"boolean","default":false}, - "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, - "blocked": {"dataType":"boolean","default":false}, - "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, - "allowPush": {"dataType":"boolean","default":false}, - "authorised": {"dataType":"boolean","default":false}, - "canceled": {"dataType":"boolean","default":false}, - "rejected": {"dataType":"boolean","default":false}, - "autoApproved": {"dataType":"boolean","default":false}, - "autoRejected": {"dataType":"boolean","default":false}, - "commitData": {"dataType":"array","array":{"dataType":"refAlias","ref":"CommitData"},"default":[]}, - "commitFrom": {"dataType":"string"}, - "commitTo": {"dataType":"string"}, - "branch": {"dataType":"string"}, - "message": {"dataType":"string"}, - "author": {"dataType":"string"}, - "user": {"dataType":"string"}, - "userEmail": {"dataType":"string"}, - "attestation": {"ref":"CompletedAttestation"}, - "rejection": {"ref":"Rejection"}, - "lastStep": {"ref":"Step"}, - "proxyGitPath": {"dataType":"string"}, - "newIdxFiles": {"dataType":"array","array":{"dataType":"string"}}, - }, - "additionalProperties": false, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + MessageResponse: { + dataType: 'refObject', + properties: { + message: { dataType: 'string', required: true }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "RejectBody": { - "dataType": "refObject", - "properties": { - "reason": {"dataType":"string","required":true}, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + CreateRepoBody: { + dataType: 'refObject', + properties: { + url: { dataType: 'string', required: true }, + name: { dataType: 'string', required: true }, + project: { dataType: 'string', required: true }, + users: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + canAuthorise: { dataType: 'array', array: { dataType: 'string' }, required: true }, + canPush: { dataType: 'array', array: { dataType: 'string' }, required: true }, }, - "additionalProperties": false, + }, + _id: { dataType: 'string' }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AuthoriseBody": { - "dataType": "refObject", - "properties": { - "params": {"dataType":"nestedObjectLiteral","nestedProperties":{"attestation":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}},"required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "ApiResources": { - "dataType": "refObject", - "properties": { - "healthcheck": {"dataType":"string","required":true}, - "push": {"dataType":"string","required":true}, - "auth": {"dataType":"string","required":true}, - }, - "additionalProperties": false, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + UsernameBody: { + dataType: 'refObject', + properties: { + username: { dataType: 'string', required: true }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "HealthResponse": { - "dataType": "refObject", - "properties": { - "message": {"dataType":"enum","enums":["ok"],"required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Link": { - "dataType": "refObject", - "properties": { - "text": {"dataType":"string","required":true}, - "url": {"dataType":"string","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "QuestionTooltip": { - "dataType": "refObject", - "properties": { - "links": {"dataType":"array","array":{"dataType":"refObject","ref":"Link"}}, - "text": {"dataType":"string","required":true}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Question": { - "dataType": "refObject", - "properties": { - "label": {"dataType":"string","required":true}, - "tooltip": {"ref":"QuestionTooltip","required":true}, - }, - "additionalProperties": false, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + Step: { + dataType: 'refObject', + properties: { + id: { dataType: 'string', required: true }, + stepName: { dataType: 'string', required: true }, + content: { dataType: 'any', required: true }, + error: { dataType: 'boolean', required: true }, + errorMessage: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + blocked: { dataType: 'boolean', required: true }, + blockedMessage: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + logs: { dataType: 'array', array: { dataType: 'string' }, default: [] }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AttestationConfig": { - "dataType": "refObject", - "properties": { - "questions": {"dataType":"array","array":{"dataType":"refObject","ref":"Question"}}, - }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "RouteAuthRule": { - "dataType": "refObject", - "properties": { - "adminOnly": {"dataType":"boolean"}, - "loginRequired": {"dataType":"boolean"}, - "pattern": {"dataType":"string"}, - }, - "additionalProperties": {"dataType":"any"}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "UIRouteAuth": { - "dataType": "refObject", - "properties": { - "enabled": {"dataType":"boolean"}, - "rules": {"dataType":"array","array":{"dataType":"refObject","ref":"RouteAuthRule"}}, - }, - "additionalProperties": {"dataType":"any"}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AuthResources": { - "dataType": "refObject", - "properties": { - "login": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, - "profile": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["get"],"required":true}},"required":true}, - "logout": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + CommitData: { + dataType: 'refAlias', + type: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + message: { dataType: 'string', required: true }, + commitTimestamp: { dataType: 'string', required: true }, + committerEmail: { dataType: 'string', required: true }, + authorEmail: { dataType: 'string', required: true }, + committer: { dataType: 'string', required: true }, + author: { dataType: 'string', required: true }, + parent: { dataType: 'string', required: true }, + tree: { dataType: 'string', required: true }, + }, + validators: {}, + }, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AttestationBase: { + dataType: 'refAlias', + type: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + automated: { dataType: 'boolean' }, + timestamp: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'datetime' }], + required: true, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "AuthConfigResponse": { - "dataType": "refObject", - "properties": { - "usernamePasswordMethod": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, - "otherMethods": {"dataType":"array","array":{"dataType":"string"},"required":true}, + reviewer: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + email: { dataType: 'string', required: true }, + username: { dataType: 'string', required: true }, + }, + required: true, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "LoginResponse": { - "dataType": "refObject", - "properties": { - "message": {"dataType":"enum","enums":["success"],"required":true}, - "user": {"ref":"PublicUser","required":true}, + }, + validators: {}, + }, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AttestationAnswer: { + dataType: 'refObject', + properties: { + label: { dataType: 'string', required: true }, + checked: { dataType: 'boolean', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + CompletedAttestation: { + dataType: 'refAlias', + type: { + dataType: 'intersection', + subSchemas: [ + { ref: 'AttestationBase' }, + { + dataType: 'nestedObjectLiteral', + nestedProperties: { + answers: { + dataType: 'array', + array: { dataType: 'refObject', ref: 'AttestationAnswer' }, + required: true, + }, + }, }, - "additionalProperties": false, + ], + validators: {}, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Express.User": { - "dataType": "refObject", - "properties": { + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + Rejection: { + dataType: 'refAlias', + type: { + dataType: 'intersection', + subSchemas: [ + { ref: 'AttestationBase' }, + { + dataType: 'nestedObjectLiteral', + nestedProperties: { reason: { dataType: 'string', required: true } }, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "LogoutResponse": { - "dataType": "refObject", - "properties": { - "isAuth": {"dataType":"boolean","required":true}, - "user": {"dataType":"union","subSchemas":[{"ref":"Express.User"},{"dataType":"undefined"}],"required":true}, + ], + validators: {}, + }, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + Action: { + dataType: 'refObject', + properties: { + id: { dataType: 'string', required: true }, + type: { dataType: 'string', required: true }, + method: { dataType: 'string', required: true }, + timestamp: { dataType: 'double', required: true }, + project: { dataType: 'string', required: true }, + repoName: { dataType: 'string', required: true }, + url: { dataType: 'string', required: true }, + repo: { dataType: 'string', required: true }, + steps: { dataType: 'array', array: { dataType: 'refObject', ref: 'Step' }, default: [] }, + error: { dataType: 'boolean', default: false }, + errorMessage: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, + blocked: { dataType: 'boolean', default: false }, + blockedMessage: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + }, + allowPush: { dataType: 'boolean', default: false }, + authorised: { dataType: 'boolean', default: false }, + canceled: { dataType: 'boolean', default: false }, + rejected: { dataType: 'boolean', default: false }, + autoApproved: { dataType: 'boolean', default: false }, + autoRejected: { dataType: 'boolean', default: false }, + commitData: { + dataType: 'array', + array: { dataType: 'refAlias', ref: 'CommitData' }, + default: [], + }, + commitFrom: { dataType: 'string' }, + commitTo: { dataType: 'string' }, + branch: { dataType: 'string' }, + message: { dataType: 'string' }, + author: { dataType: 'string' }, + user: { dataType: 'string' }, + userEmail: { dataType: 'string' }, + attestation: { ref: 'CompletedAttestation' }, + rejection: { ref: 'Rejection' }, + lastStep: { ref: 'Step' }, + proxyGitPath: { dataType: 'string' }, + newIdxFiles: { dataType: 'array', array: { dataType: 'string' } }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + RejectBody: { + dataType: 'refObject', + properties: { + reason: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AuthoriseBody: { + dataType: 'refObject', + properties: { + params: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + attestation: { + dataType: 'array', + array: { dataType: 'refObject', ref: 'AttestationAnswer' }, + required: true, + }, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "GitAccountBody": { - "dataType": "refObject", - "properties": { - "username": {"dataType":"string"}, - "id": {"dataType":"string"}, - "gitAccount": {"dataType":"string","required":true}, + required: true, + }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + ApiResources: { + dataType: 'refObject', + properties: { + healthcheck: { dataType: 'string', required: true }, + push: { dataType: 'string', required: true }, + auth: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + HealthResponse: { + dataType: 'refObject', + properties: { + message: { dataType: 'enum', enums: ['ok'], required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + Link: { + dataType: 'refObject', + properties: { + text: { dataType: 'string', required: true }, + url: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + QuestionTooltip: { + dataType: 'refObject', + properties: { + links: { dataType: 'array', array: { dataType: 'refObject', ref: 'Link' } }, + text: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + Question: { + dataType: 'refObject', + properties: { + label: { dataType: 'string', required: true }, + tooltip: { ref: 'QuestionTooltip', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AttestationConfig: { + dataType: 'refObject', + properties: { + questions: { dataType: 'array', array: { dataType: 'refObject', ref: 'Question' } }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + RouteAuthRule: { + dataType: 'refObject', + properties: { + adminOnly: { dataType: 'boolean' }, + loginRequired: { dataType: 'boolean' }, + pattern: { dataType: 'string' }, + }, + additionalProperties: { dataType: 'any' }, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + UIRouteAuth: { + dataType: 'refObject', + properties: { + enabled: { dataType: 'boolean' }, + rules: { dataType: 'array', array: { dataType: 'refObject', ref: 'RouteAuthRule' } }, + }, + additionalProperties: { dataType: 'any' }, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AuthResources: { + dataType: 'refObject', + properties: { + login: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + uri: { dataType: 'string', required: true }, + action: { dataType: 'enum', enums: ['post'], required: true }, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CreateUserResponse": { - "dataType": "refObject", - "properties": { - "message": {"dataType":"string","required":true}, - "username": {"dataType":"string","required":true}, + required: true, + }, + profile: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + uri: { dataType: 'string', required: true }, + action: { dataType: 'enum', enums: ['get'], required: true }, }, - "additionalProperties": false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CreateUserBody": { - "dataType": "refObject", - "properties": { - "username": {"dataType":"string","required":true}, - "password": {"dataType":"string","required":true}, - "email": {"dataType":"string","required":true}, - "gitAccount": {"dataType":"string","required":true}, - "admin": {"dataType":"boolean"}, + required: true, + }, + logout: { + dataType: 'nestedObjectLiteral', + nestedProperties: { + uri: { dataType: 'string', required: true }, + action: { dataType: 'enum', enums: ['post'], required: true }, }, - "additionalProperties": false, + required: true, + }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + AuthConfigResponse: { + dataType: 'refObject', + properties: { + usernamePasswordMethod: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + otherMethods: { dataType: 'array', array: { dataType: 'string' }, required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + LoginResponse: { + dataType: 'refObject', + properties: { + message: { dataType: 'enum', enums: ['success'], required: true }, + user: { ref: 'PublicUser', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + 'Express.User': { + dataType: 'refObject', + properties: {}, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + LogoutResponse: { + dataType: 'refObject', + properties: { + isAuth: { dataType: 'boolean', required: true }, + user: { + dataType: 'union', + subSchemas: [{ ref: 'Express.User' }, { dataType: 'undefined' }], + required: true, + }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + GitAccountBody: { + dataType: 'refObject', + properties: { + username: { dataType: 'string' }, + id: { dataType: 'string' }, + gitAccount: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + CreateUserResponse: { + dataType: 'refObject', + properties: { + message: { dataType: 'string', required: true }, + username: { dataType: 'string', required: true }, + }, + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + CreateUserBody: { + dataType: 'refObject', + properties: { + username: { dataType: 'string', required: true }, + password: { dataType: 'string', required: true }, + email: { dataType: 'string', required: true }, + gitAccount: { dataType: 'string', required: true }, + admin: { dataType: 'boolean' }, }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + additionalProperties: false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa }; -const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); +const templateService = new ExpressTemplateService(models, { + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, +}); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +export function RegisterRoutes(app: Router) { + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + + const argsUserController_getUsers: Record = {}; + app.get( + '/api/v1/user', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(UserController), + ...fetchMiddlewares(UserController.prototype.getUsers), + + async function UserController_getUsers(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsUserController_getUsers, + request, + response, + }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getUsers', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_getUser: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.get( + '/api/v1/user/:id', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(UserController), + ...fetchMiddlewares(UserController.prototype.getUser), + + async function UserController_getUser(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsUserController_getUser, + request, + response, + }); + const controller = new UserController(); + await templateService.apiHandler({ + methodName: 'getUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepos: Record = { + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + }; + app.get( + '/api/v1/repo', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.getRepos), + + async function RepoController_getRepos(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_getRepos, + request, + response, + }); -export function RegisterRoutes(app: Router) { + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepos', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepo: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.get( + '/api/v1/repo/:id', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.getRepo), + + async function RepoController_getRepo(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_getRepo, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_createRepo: Record = { + body: { in: 'body', name: 'body', required: true, ref: 'CreateRepoBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + validationErrorResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + conflictResponse: { + in: 'res', + name: '409', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + internalServerErrorResponse: { + in: 'res', + name: '500', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/v1/repo', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.createRepo), + + async function RepoController_createRepo(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_createRepo, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'createRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addPushUser: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + body: { in: 'body', name: 'body', required: true, ref: 'UsernameBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + userNotFoundResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { error: { dataType: 'string', required: true } }, + }, + }; + app.patch( + '/api/v1/repo/:id/user/push', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.addPushUser), + + async function RepoController_addPushUser(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_addPushUser, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addPushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addAuthoriseUser: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + body: { in: 'body', name: 'body', required: true, ref: 'UsernameBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + userNotFoundResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { error: { dataType: 'string', required: true } }, + }, + }; + app.patch( + '/api/v1/repo/:id/user/authorise', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.addAuthoriseUser), + + async function RepoController_addAuthoriseUser( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_addAuthoriseUser, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removeAuthoriseUser: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + username: { in: 'path', name: 'username', required: true, dataType: 'string' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + userNotFoundResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { error: { dataType: 'string', required: true } }, + }, + }; + app.delete( + '/api/v1/repo/:id/user/authorise/:username', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.removeAuthoriseUser), + + async function RepoController_removeAuthoriseUser( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_removeAuthoriseUser, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removeAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removePushUser: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + username: { in: 'path', name: 'username', required: true, dataType: 'string' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + userNotFoundResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { error: { dataType: 'string', required: true } }, + }, + }; + app.delete( + '/api/v1/repo/:id/user/push/:username', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.removePushUser), + + async function RepoController_removePushUser( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_removePushUser, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removePushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_deleteRepo: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.delete( + '/api/v1/repo/:id/delete', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(RepoController), + ...fetchMiddlewares(RepoController.prototype.deleteRepo), + + async function RepoController_deleteRepo(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsRepoController_deleteRepo, + request, + response, + }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'deleteRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPushes: Record = { + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + }; + app.get( + '/api/v1/push', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(PushController), + ...fetchMiddlewares(PushController.prototype.getPushes), + + async function PushController_getPushes(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsPushController_getPushes, + request, + response, + }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPushes', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPush: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.get( + '/api/v1/push/:id', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(PushController), + ...fetchMiddlewares(PushController.prototype.getPush), + + async function PushController_getPush(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsPushController_getPush, + request, + response, + }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_rejectPush: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + body: { in: 'body', name: 'body', required: true, ref: 'RejectBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + validationErrorResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + forbiddenResponse: { + in: 'res', + name: '403', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/v1/push/:id/reject', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(PushController), + ...fetchMiddlewares(PushController.prototype.rejectPush), + + async function PushController_rejectPush(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsPushController_rejectPush, + request, + response, + }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'rejectPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_authorisePush: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + body: { in: 'body', name: 'body', required: true, ref: 'AuthoriseBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + validationErrorResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + forbiddenResponse: { + in: 'res', + name: '403', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/v1/push/:id/authorise', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(PushController), + ...fetchMiddlewares(PushController.prototype.authorisePush), + + async function PushController_authorisePush( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsPushController_authorisePush, + request, + response, + }); - // ########################################################################################################### - // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look - // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa - // ########################################################################################################### - - - - const argsUserController_getUsers: Record = { - }; - app.get('/api/v1/user', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(UserController)), - ...(fetchMiddlewares(UserController.prototype.getUsers)), - - async function UserController_getUsers(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUsers, request, response }); - - const controller = new UserController(); - - await templateService.apiHandler({ - methodName: 'getUsers', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'authorisePush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsUserController_getUser: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.get('/api/v1/user/:id', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(UserController)), - ...(fetchMiddlewares(UserController.prototype.getUser)), - - async function UserController_getUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUser, request, response }); - - const controller = new UserController(); - - await templateService.apiHandler({ - methodName: 'getUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_cancelPush: Record = { + id: { in: 'path', name: 'id', required: true, dataType: 'string' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + forbiddenResponse: { + in: 'res', + name: '403', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/v1/push/:id/cancel', + authenticateMiddleware([{ jwt: [] }]), + ...fetchMiddlewares(PushController), + ...fetchMiddlewares(PushController.prototype.cancelPush), + + async function PushController_cancelPush(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsPushController_cancelPush, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_getRepos: Record = { - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - }; - app.get('/api/v1/repo', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.getRepos)), - - async function RepoController_getRepos(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepos, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'getRepos', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'cancelPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_getRepo: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.get('/api/v1/repo/:id', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.getRepo)), - - async function RepoController_getRepo(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepo, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'getRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHomeController_getResources: Record = {}; + app.get( + '/api', + ...fetchMiddlewares(HomeController), + ...fetchMiddlewares(HomeController.prototype.getResources), + + async function HomeController_getResources( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsHomeController_getResources, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_createRepo: Record = { - body: {"in":"body","name":"body","required":true,"ref":"CreateRepoBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - conflictResponse: {"in":"res","name":"409","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/v1/repo', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.createRepo)), - - async function RepoController_createRepo(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_createRepo, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'createRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new HomeController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_addPushUser: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, - }; - app.patch('/api/v1/repo/:id/user/push', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.addPushUser)), - - async function RepoController_addPushUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addPushUser, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'addPushUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHealthController_check: Record = {}; + app.get( + '/api/v1/healthcheck', + ...fetchMiddlewares(HealthController), + ...fetchMiddlewares(HealthController.prototype.check), + + async function HealthController_check(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsHealthController_check, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_addAuthoriseUser: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, - }; - app.patch('/api/v1/repo/:id/user/authorise', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.addAuthoriseUser)), - - async function RepoController_addAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addAuthoriseUser, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'addAuthoriseUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new HealthController(); + + await templateService.apiHandler({ + methodName: 'check', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getAttestation: Record = {}; + app.get( + '/api/v1/config/attestation', + ...fetchMiddlewares(ConfigController), + ...fetchMiddlewares(ConfigController.prototype.getAttestation), + + async function ConfigController_getAttestation( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsConfigController_getAttestation, + request, + response, + }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getAttestation', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_removeAuthoriseUser: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - username: {"in":"path","name":"username","required":true,"dataType":"string"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, - }; - app.delete('/api/v1/repo/:id/user/authorise/:username', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.removeAuthoriseUser)), - - async function RepoController_removeAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removeAuthoriseUser, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'removeAuthoriseUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUrlShortener: Record = {}; + app.get( + '/api/v1/config/urlShortener', + ...fetchMiddlewares(ConfigController), + ...fetchMiddlewares(ConfigController.prototype.getUrlShortener), + + async function ConfigController_getUrlShortener( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsConfigController_getUrlShortener, + request, + response, + }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUrlShortener', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_removePushUser: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - username: {"in":"path","name":"username","required":true,"dataType":"string"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, - }; - app.delete('/api/v1/repo/:id/user/push/:username', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.removePushUser)), - - async function RepoController_removePushUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removePushUser, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'removePushUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getContactEmail: Record = {}; + app.get( + '/api/v1/config/contactEmail', + ...fetchMiddlewares(ConfigController), + ...fetchMiddlewares(ConfigController.prototype.getContactEmail), + + async function ConfigController_getContactEmail( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsConfigController_getContactEmail, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_deleteRepo: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.delete('/api/v1/repo/:id/delete', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(RepoController)), - ...(fetchMiddlewares(RepoController.prototype.deleteRepo)), - - async function RepoController_deleteRepo(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_deleteRepo, request, response }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'deleteRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getContactEmail', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_getPushes: Record = { - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - }; - app.get('/api/v1/push', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(PushController)), - ...(fetchMiddlewares(PushController.prototype.getPushes)), - - async function PushController_getPushes(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPushes, request, response }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'getPushes', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUiRouteAuth: Record = {}; + app.get( + '/api/v1/config/uiRouteAuth', + ...fetchMiddlewares(ConfigController), + ...fetchMiddlewares(ConfigController.prototype.getUiRouteAuth), + + async function ConfigController_getUiRouteAuth( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsConfigController_getUiRouteAuth, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_getPush: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.get('/api/v1/push/:id', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(PushController)), - ...(fetchMiddlewares(PushController.prototype.getPush)), - - async function PushController_getPush(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPush, request, response }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'getPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUiRouteAuth', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_rejectPush: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - body: {"in":"body","name":"body","required":true,"ref":"RejectBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/v1/push/:id/reject', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(PushController)), - ...(fetchMiddlewares(PushController.prototype.rejectPush)), - - async function PushController_rejectPush(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsPushController_rejectPush, request, response }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'rejectPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getResources: Record = {}; + app.get( + '/api/auth', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.getResources), + + async function AuthController_getResources( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_getResources, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_authorisePush: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - body: {"in":"body","name":"body","required":true,"ref":"AuthoriseBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/v1/push/:id/authorise', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(PushController)), - ...(fetchMiddlewares(PushController.prototype.authorisePush)), - - async function PushController_authorisePush(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsPushController_authorisePush, request, response }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'authorisePush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_cancelPush: Record = { - id: {"in":"path","name":"id","required":true,"dataType":"string"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/v1/push/:id/cancel', - authenticateMiddleware([{"jwt":[]}]), - ...(fetchMiddlewares(PushController)), - ...(fetchMiddlewares(PushController.prototype.cancelPush)), - - async function PushController_cancelPush(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsPushController_cancelPush, request, response }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'cancelPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getAuthConfig: Record = {}; + app.get( + '/api/auth/config', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.getAuthConfig), + + async function AuthController_getAuthConfig( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_getAuthConfig, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsHomeController_getResources: Record = { - }; - app.get('/api', - ...(fetchMiddlewares(HomeController)), - ...(fetchMiddlewares(HomeController.prototype.getResources)), - - async function HomeController_getResources(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsHomeController_getResources, request, response }); - - const controller = new HomeController(); - - await templateService.apiHandler({ - methodName: 'getResources', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getAuthConfig', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsHealthController_check: Record = { - }; - app.get('/api/v1/healthcheck', - ...(fetchMiddlewares(HealthController)), - ...(fetchMiddlewares(HealthController.prototype.check)), - - async function HealthController_check(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsHealthController_check, request, response }); - - const controller = new HealthController(); - - await templateService.apiHandler({ - methodName: 'check', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_login: Record = { + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + }; + app.post( + '/api/auth/login', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.login), + + async function AuthController_login(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_login, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getAttestation: Record = { - }; - app.get('/api/v1/config/attestation', - ...(fetchMiddlewares(ConfigController)), - ...(fetchMiddlewares(ConfigController.prototype.getAttestation)), - - async function ConfigController_getAttestation(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getAttestation, request, response }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getAttestation', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'login', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getUrlShortener: Record = { - }; - app.get('/api/v1/config/urlShortener', - ...(fetchMiddlewares(ConfigController)), - ...(fetchMiddlewares(ConfigController.prototype.getUrlShortener)), - - async function ConfigController_getUrlShortener(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUrlShortener, request, response }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getUrlShortener', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_initiateOIDC: Record = {}; + app.get( + '/api/auth/openidconnect', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.initiateOIDC), + + async function AuthController_initiateOIDC( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_initiateOIDC, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getContactEmail: Record = { - }; - app.get('/api/v1/config/contactEmail', - ...(fetchMiddlewares(ConfigController)), - ...(fetchMiddlewares(ConfigController.prototype.getContactEmail)), - - async function ConfigController_getContactEmail(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getContactEmail, request, response }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getContactEmail', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'initiateOIDC', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getUiRouteAuth: Record = { - }; - app.get('/api/v1/config/uiRouteAuth', - ...(fetchMiddlewares(ConfigController)), - ...(fetchMiddlewares(ConfigController.prototype.getUiRouteAuth)), - - async function ConfigController_getUiRouteAuth(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUiRouteAuth, request, response }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getUiRouteAuth', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_handleOIDCCallback: Record = {}; + app.get( + '/api/auth/openidconnect/callback', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.handleOIDCCallback), + + async function AuthController_handleOIDCCallback( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_handleOIDCCallback, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getResources: Record = { - }; - app.get('/api/auth', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.getResources)), - - async function AuthController_getResources(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getResources, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getResources', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'handleOIDCCallback', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getAuthConfig: Record = { - }; - app.get('/api/auth/config', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.getAuthConfig)), - - async function AuthController_getAuthConfig(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getAuthConfig, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getAuthConfig', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_logout: Record = { + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + }; + app.post( + '/api/auth/logout', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.logout), + + async function AuthController_logout(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_logout, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_login: Record = { - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - }; - app.post('/api/auth/login', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.login)), - - async function AuthController_login(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_login, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'login', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'logout', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_initiateOIDC: Record = { - }; - app.get('/api/auth/openidconnect', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.initiateOIDC)), - - async function AuthController_initiateOIDC(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_initiateOIDC, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'initiateOIDC', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getProfile: Record = { + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.get( + '/api/auth/profile', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.getProfile), + + async function AuthController_getProfile(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_getProfile, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_handleOIDCCallback: Record = { - }; - app.get('/api/auth/openidconnect/callback', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.handleOIDCCallback)), - - async function AuthController_handleOIDCCallback(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_handleOIDCCallback, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'handleOIDCCallback', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getProfile', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_logout: Record = { - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - }; - app.post('/api/auth/logout', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.logout)), - - async function AuthController_logout(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_logout, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'logout', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_updateGitAccount: Record = { + body: { in: 'body', name: 'body', required: true, ref: 'GitAccountBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '401', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + notFoundResponse: { + in: 'res', + name: '404', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + validationErrorResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + forbiddenResponse: { + in: 'res', + name: '403', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + internalServerErrorResponse: { + in: 'res', + name: '500', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/auth/gitAccount', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.updateGitAccount), + + async function AuthController_updateGitAccount( + request: ExRequest, + response: ExResponse, + next: any, + ) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_updateGitAccount, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getProfile: Record = { - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.get('/api/auth/profile', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.getProfile)), - - async function AuthController_getProfile(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getProfile, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getProfile', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'updateGitAccount', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_updateGitAccount: Record = { - body: {"in":"body","name":"body","required":true,"ref":"GitAccountBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/auth/gitAccount', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.updateGitAccount)), - - async function AuthController_updateGitAccount(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_updateGitAccount, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'updateGitAccount', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_createUser: Record = { + body: { in: 'body', name: 'body', required: true, ref: 'CreateUserBody' }, + req: { in: 'request', name: 'req', required: true, dataType: 'object' }, + unauthorisedResponse: { + in: 'res', + name: '403', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + validationErrorResponse: { + in: 'res', + name: '400', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + internalServerErrorResponse: { + in: 'res', + name: '500', + required: true, + dataType: 'nestedObjectLiteral', + nestedProperties: { message: { dataType: 'string', required: true } }, + }, + }; + app.post( + '/api/auth/create-user', + ...fetchMiddlewares(AuthController), + ...fetchMiddlewares(AuthController.prototype.createUser), + + async function AuthController_createUser(request: ExRequest, response: ExResponse, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args: argsAuthController_createUser, + request, + response, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_createUser: Record = { - body: {"in":"body","name":"body","required":true,"ref":"CreateUserBody"}, - req: {"in":"request","name":"req","required":true,"dataType":"object"}, - unauthorisedResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, - }; - app.post('/api/auth/create-user', - ...(fetchMiddlewares(AuthController)), - ...(fetchMiddlewares(AuthController.prototype.createUser)), - - async function AuthController_createUser(request: ExRequest, response: ExResponse, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_createUser, request, response }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'createUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'createUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, }); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - function authenticateMiddleware(security: TsoaRoute.Security[] = []) { - return async function runAuthenticationMiddleware(request: any, response: any, next: any) { - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - // keep track of failed auth attempts so we can hand back the most - // recent one. This behavior was previously existing so preserving it - // here - const failedAttempts: any[] = []; - const pushAndRethrow = (error: any) => { - failedAttempts.push(error); - throw error; - }; - - const secMethodOrPromises: Promise[] = []; - for (const secMethod of security) { - if (Object.keys(secMethod).length > 1) { - const secMethodAndPromises: Promise[] = []; - - for (const name in secMethod) { - secMethodAndPromises.push( - expressAuthenticationRecasted(request, name, secMethod[name], response) - .catch(pushAndRethrow) - ); - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - secMethodOrPromises.push(Promise.all(secMethodAndPromises) - .then(users => { return users[0]; })); - } else { - for (const name in secMethod) { - secMethodOrPromises.push( - expressAuthenticationRecasted(request, name, secMethod[name], response) - .catch(pushAndRethrow) - ); - } - } - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - try { - request['user'] = await Promise.any(secMethodOrPromises); - - // Response was sent in middleware, abort - if (response.writableEnded) { - return; - } - - next(); - } - catch(err) { - // Show most recent error as response - const error = failedAttempts.pop(); - error.status = error.status || 401; - - // Response was sent in middleware, abort - if (response.writableEnded) { - return; - } - next(error); - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + } catch (err) { + return next(err); + } + }, + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return async function runAuthenticationMiddleware(request: any, response: any, next: any) { + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // keep track of failed auth attempts so we can hand back the most + // recent one. This behavior was previously existing so preserving it + // here + const failedAttempts: any[] = []; + const pushAndRethrow = (error: any) => { + failedAttempts.push(error); + throw error; + }; + + const secMethodOrPromises: Promise[] = []; + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + const secMethodAndPromises: Promise[] = []; + + for (const name in secMethod) { + secMethodAndPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response).catch( + pushAndRethrow, + ), + ); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + secMethodOrPromises.push( + Promise.all(secMethodAndPromises).then((users) => { + return users[0]; + }), + ); + } else { + for (const name in secMethod) { + secMethodOrPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response).catch( + pushAndRethrow, + ), + ); + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + try { + request['user'] = await Promise.any(secMethodOrPromises); + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; + } + + next(); + } catch (err) { + // Show most recent error as response + const error = failedAttempts.pop(); + error.status = error.status || 401; + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; } - } + next(error); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + }; + } - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa } // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa From 3656bc1025c7929cab56acb6f55a03798892dc2e Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 5 Jun 2026 18:16:08 +0900 Subject: [PATCH 15/18] chore: npm audit fix --- package-lock.json | 1239 +++++++++++++++++++-------------------------- 1 file changed, 521 insertions(+), 718 deletions(-) diff --git a/package-lock.json b/package-lock.json index 556654db3..cea8108cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1770,9 +1770,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", - "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-4.0.1.tgz", + "integrity": "sha512-y20e+e6dFYkOUUJLVUZTsJRuTiXZaUQ32WD+R/ux/HBybbTx4ge7cNINcua0pU8+SNkKuRbOF12mBmzuzM8n5w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1789,22 +1789,13 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.14.1", + "qs": "^6.15.2", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" + "tunnel-agent": "^0.6.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/request/node_modules/uuid": { - "version": "8.3.2", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "node": ">= 14.17.0" } }, "node_modules/@cypress/xvfb": { @@ -2633,9 +2624,9 @@ } }, "node_modules/@hapi/content": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.1.tgz", - "integrity": "sha512-lQ2vOoFMNYxwKVnKf+3Pi3PfoviM4EJYlT9JbrBPfEc0xKMiVDqqXF8UTE1S1oKhHQliWSP5t6zTKNlmaXBGcQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.2.tgz", + "integrity": "sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.0" @@ -2853,9 +2844,9 @@ } }, "node_modules/@hapi/wreck": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", - "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.2.tgz", + "integrity": "sha512-3dMnV2pfhQiyEqu8DL3VBmxkdLiRDiiUDuG79Dp+UK1gL9ZxAfDOUhB6k3D5MLqcgJJ1IARyGFhwoc1NITr/pg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/boom": "^10.0.1", @@ -4598,9 +4589,9 @@ } }, "node_modules/@tsoa/runtime/node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -4611,7 +4602,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -4655,14 +4646,14 @@ "license": "MIT" }, "node_modules/@tsoa/runtime/node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -4681,7 +4672,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -4888,6 +4879,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "license": "MIT", @@ -5320,15 +5322,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", @@ -5633,9 +5626,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.6.tgz", + "integrity": "sha512-LsAdmUapA0qSN306d8+zOyawM0hFm2m2Hg9IwVNIKBm+qJV8cijiq2c+gxKZcB1HCfIWAy+0qEZDCUQA58A1cw==", "dev": true, "license": "MIT", "dependencies": { @@ -5657,8 +5650,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "3.2.6", + "vitest": "3.2.6" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -5723,15 +5716,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -5739,84 +5732,37 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*" - } - }, - "node_modules/@vitest/expect/node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/@vitest/expect/node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", "dev": true, "license": "MIT", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/expect/node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/@vitest/expect/node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@vitest/expect/node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/expect/node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", "dev": true, "license": "MIT", "dependencies": { @@ -5827,13 +5773,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", + "@vitest/utils": "3.2.6", "pathe": "^2.0.3", "strip-literal": "^3.0.0" }, @@ -5842,13 +5788,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.6", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -5857,9 +5803,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", "dev": true, "license": "MIT", "dependencies": { @@ -5870,13 +5816,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.6", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, @@ -5884,13 +5830,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/abbrev": { "version": "1.1.1", "license": "ISC" @@ -6028,34 +5967,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { - "version": "4.3.2", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" + "environment": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6298,6 +6220,16 @@ "node": ">=0.8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-v8-to-istanbul": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.7.tgz", @@ -6317,14 +6249,6 @@ "dev": true, "license": "MIT" }, - "node_modules/astral-regex": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "3.2.5", "license": "MIT" @@ -6368,6 +6292,8 @@ }, "node_modules/aws-sign2": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6376,6 +6302,8 @@ }, "node_modules/aws4": { "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, "license": "MIT" }, @@ -6450,6 +6378,8 @@ }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6630,14 +6560,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -6696,9 +6618,9 @@ } }, "node_modules/c8/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -6894,9 +6816,28 @@ }, "node_modules/caseless": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true, "license": "Apache-2.0" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -6935,6 +6876,16 @@ "node": ">=8" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/ci-info": { "version": "4.3.0", "funding": [ @@ -6961,14 +6912,19 @@ } }, "node_modules/cli-cursor": { - "version": "3.1.0", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-table3": { @@ -7004,36 +6960,66 @@ } }, "node_modules/cli-truncate": { - "version": "2.1.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" }, "engines": { - "node": ">=8" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "8.0.0", + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "4.2.3", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/cliui": { @@ -7476,14 +7462,14 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "15.9.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.9.0.tgz", - "integrity": "sha512-Ks6Bdilz3TtkLZtTQyqYaqtL/WT3X3APKaSLhTV96TmTyudzSjc6EJsJCHmBb7DxO+3R12q3Jkbjgm/iPgmwfg==", + "version": "15.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.16.0.tgz", + "integrity": "sha512-fy0M0c9xDLEp4v9y7LLKFeAQhIdDsobxDSKpD3JcZpqQefjy9TSzEyVV3HA0zu7hUi0bGHlSYlI7ASub8wgR9A==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.10", + "@cypress/request": "^4.0.0", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -7492,26 +7478,22 @@ "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", - "cachedir": "^2.3.0", + "cachedir": "^2.4.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", - "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", + "listr2": "^9.0.5", + "lodash": "^4.17.23", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", @@ -7520,11 +7502,12 @@ "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "supports-color": "^8.1.1", - "systeminformation": "^5.27.14", + "systeminformation": "^5.31.1", "tmp": "~0.2.4", "tree-kill": "1.2.2", + "tslib": "1.14.1", "untildify": "^4.0.0", - "yauzl": "^2.10.0" + "yauzl": "^3.3.1" }, "bin": { "cypress": "bin/cypress" @@ -7540,6 +7523,13 @@ "dev": true, "license": "MIT" }, + "node_modules/cypress/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/dargs": { "version": "8.1.0", "dev": true, @@ -7553,6 +7543,8 @@ }, "node_modules/dashdash": { "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "license": "MIT", "dependencies": { @@ -7653,6 +7645,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -7871,6 +7873,8 @@ }, "node_modules/ecc-jsbn": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "license": "MIT", "dependencies": { @@ -7917,18 +7921,6 @@ "once": "^1.4.0" } }, - "node_modules/enquirer": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "1.1.2", "license": "BSD-2-Clause" @@ -8699,28 +8691,11 @@ }, "node_modules/extend": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/extsprintf": { "version": "1.4.1", "engines": [ @@ -8825,38 +8800,6 @@ "fxparser": "src/cli/cli.js" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -9029,6 +8972,8 @@ }, "node_modules/forever-agent": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9185,9 +9130,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -9281,6 +9226,8 @@ }, "node_modules/getpass": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "license": "MIT", "dependencies": { @@ -9635,6 +9582,8 @@ }, "node_modules/http-signature": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, "license": "MIT", "dependencies": { @@ -10369,6 +10318,8 @@ }, "node_modules/isstream": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true, "license": "MIT" }, @@ -10613,6 +10564,8 @@ }, "node_modules/jsbn": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true, "license": "MIT" }, @@ -10639,6 +10592,8 @@ }, "node_modules/json-schema": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, @@ -10654,6 +10609,8 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC" }, @@ -10737,6 +10694,8 @@ }, "node_modules/jsprim": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", "dev": true, "engines": [ "node >=0.6.0" @@ -10751,6 +10710,8 @@ }, "node_modules/jsprim/node_modules/extsprintf": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true, "engines": [ "node >=0.6.0" @@ -10759,6 +10720,8 @@ }, "node_modules/jsprim/node_modules/verror": { "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "engines": [ "node >=0.6.0" @@ -10977,81 +10940,6 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/cli-truncate": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^7.1.0", - "string-width": "^8.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", @@ -11062,30 +10950,7 @@ "node": ">=20" } }, - "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/listr2": { + "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", @@ -11103,114 +10968,65 @@ "node": ">=20.0.0" } }, - "node_modules/lint-staged/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/lint-staged/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/lint-staged/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } + "license": "MIT" }, - "node_modules/lint-staged/node_modules/string-width": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", - "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.3.0", + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -11219,7 +11035,7 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/wrap-ansi": { + "node_modules/listr2/node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", @@ -11237,98 +11053,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2": { - "version": "3.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/listr2/node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/load-plugin": { "version": "6.0.3", "license": "MIT", @@ -11441,83 +11165,157 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "get-east-asian-width": "^1.3.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update": { - "version": "4.0.0", + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/loose-envify": { @@ -11530,6 +11328,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -12613,11 +12418,23 @@ "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pause": { "version": "0.0.1" }, "node_modules/pend": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" }, @@ -12627,6 +12444,8 @@ }, "node_modules/performance-now": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true, "license": "MIT" }, @@ -12905,9 +12724,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -13386,15 +13205,49 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rfdc": { @@ -13891,16 +13744,49 @@ } }, "node_modules/slice-ansi": { - "version": "3.0.0", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/smart-buffer": { @@ -13992,6 +13878,8 @@ }, "node_modules/sshpk": { "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14281,9 +14169,9 @@ } }, "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { @@ -14565,9 +14453,9 @@ } }, "node_modules/tinyspy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -14576,6 +14464,8 @@ }, "node_modules/tldts": { "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14587,11 +14477,15 @@ }, "node_modules/tldts-core": { "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, "license": "MIT" }, "node_modules/tmp": { - "version": "0.2.5", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", "engines": { @@ -14630,6 +14524,8 @@ }, "node_modules/tough-cookie": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -14800,6 +14696,8 @@ }, "node_modules/tunnel-agent": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14811,6 +14709,8 @@ }, "node_modules/tweetnacl": { "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true, "license": "Unlicense" }, @@ -15379,20 +15279,20 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -15422,8 +15322,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", "happy-dom": "*", "jsdom": "*" }, @@ -15451,107 +15351,6 @@ } } }, - "node_modules/vitest/node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*" - } - }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/vitest/node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/vitest/node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitest/node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -15914,12 +15713,16 @@ } }, "node_modules/yauzl": { - "version": "2.10.0", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.2.tgz", + "integrity": "sha512-Md9ankxxN23wncAN8s7+Tn3Co52zLUPMtnrLAbVCnfG5d2tKBFfmygYSgXlqFgXObtzIgqkx7aNgDBpso9+4qA==", "dev": true, "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/yn": { From 445a61aece065feb30151efc5b418ab7a90ea4df Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Tue, 9 Jun 2026 03:47:30 +0200 Subject: [PATCH 16/18] feat(controllers): apply changes from main on auth route for getting host and port from config --- src/service/controllers/AuthController.ts | 7 +- src/service/generatedRoutes.ts | 3131 +++++++++------------ 2 files changed, 1297 insertions(+), 1841 deletions(-) diff --git a/src/service/controllers/AuthController.ts b/src/service/controllers/AuthController.ts index ed8b885cf..f124e7e96 100644 --- a/src/service/controllers/AuthController.ts +++ b/src/service/controllers/AuthController.ts @@ -17,7 +17,7 @@ import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'; import { Body, Controller, Get, Middlewares, Post, Request, Res, Route, Tags } from 'tsoa'; import { getPassport, authStrategies } from '../passport'; -import { getAuthMethods } from '../../config'; +import { getAuthMethods, getUIHost, getUIPort } from '../../config'; import * as db from '../../db'; import * as passportLocal from '../passport/local'; import * as passportAD from '../passport/activeDirectory'; @@ -43,9 +43,6 @@ import { ValidationErrorResponse, } from '../decorators/response.types'; -const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost', GIT_PROXY_UI_PORT: uiPort = 3000 } = - process.env; - // login strategies that will work with /login e.g. take username and password const appropriateLoginStrategies = [passportLocal.type, passportAD.type]; @@ -110,7 +107,7 @@ export function oidcCallbackMiddleware( return res.status(500).end(); } console.log('Logged in successfully. User:', user); - return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); + return res.redirect(`${getUIHost()}:${getUIPort()}/dashboard/profile`); }); }, )(req, res, next); diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts index 9cb9caa4c..588635686 100644 --- a/src/service/generatedRoutes.ts +++ b/src/service/generatedRoutes.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import type { TsoaRoute } from '@tsoa/runtime'; -import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; +import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { UserController } from './controllers/UserController'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa @@ -21,1903 +21,1362 @@ import { expressAuthentication } from './authentication'; // @ts-ignore - no great way to install types from subpackage import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; -const expressAuthenticationRecasted = expressAuthentication as ( - req: ExRequest, - securityName: string, - scopes?: string[], - res?: ExResponse, -) => Promise; +const expressAuthenticationRecasted = expressAuthentication as (req: ExRequest, securityName: string, scopes?: string[], res?: ExResponse) => Promise; + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const models: TsoaRoute.Models = { - PublicUser: { - dataType: 'refObject', - properties: { - username: { dataType: 'string', required: true }, - displayName: { dataType: 'string', required: true }, - email: { dataType: 'string', required: true }, - title: { dataType: 'string', required: true }, - gitAccount: { dataType: 'string', required: true }, - admin: { dataType: 'boolean', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - RepoWithProxy: { - dataType: 'refObject', - properties: { - project: { dataType: 'string', required: true }, - name: { dataType: 'string', required: true }, - url: { dataType: 'string', required: true }, - users: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - canAuthorise: { dataType: 'array', array: { dataType: 'string' }, required: true }, - canPush: { dataType: 'array', array: { dataType: 'string' }, required: true }, + "PublicUser": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, + "displayName": {"dataType":"string","required":true}, + "email": {"dataType":"string","required":true}, + "title": {"dataType":"string","required":true}, + "gitAccount": {"dataType":"string","required":true}, + "admin": {"dataType":"boolean","required":true}, }, - required: true, - }, - _id: { dataType: 'string' }, - proxyURL: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - MessageResponse: { - dataType: 'refObject', - properties: { - message: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - CreateRepoBody: { - dataType: 'refObject', - properties: { - url: { dataType: 'string', required: true }, - name: { dataType: 'string', required: true }, - project: { dataType: 'string', required: true }, - users: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - canAuthorise: { dataType: 'array', array: { dataType: 'string' }, required: true }, - canPush: { dataType: 'array', array: { dataType: 'string' }, required: true }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RepoWithProxy": { + "dataType": "refObject", + "properties": { + "project": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, + "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, + "_id": {"dataType":"string"}, + "proxyURL": {"dataType":"string","required":true}, }, - }, - _id: { dataType: 'string' }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - UsernameBody: { - dataType: 'refObject', - properties: { - username: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - Step: { - dataType: 'refObject', - properties: { - id: { dataType: 'string', required: true }, - stepName: { dataType: 'string', required: true }, - content: { dataType: 'any', required: true }, - error: { dataType: 'boolean', required: true }, - errorMessage: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - required: true, - }, - blocked: { dataType: 'boolean', required: true }, - blockedMessage: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - required: true, - }, - logs: { dataType: 'array', array: { dataType: 'string' }, default: [] }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - CommitData: { - dataType: 'refAlias', - type: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - message: { dataType: 'string', required: true }, - commitTimestamp: { dataType: 'string', required: true }, - committerEmail: { dataType: 'string', required: true }, - authorEmail: { dataType: 'string', required: true }, - committer: { dataType: 'string', required: true }, - author: { dataType: 'string', required: true }, - parent: { dataType: 'string', required: true }, - tree: { dataType: 'string', required: true }, - }, - validators: {}, + "additionalProperties": false, }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AttestationBase: { - dataType: 'refAlias', - type: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - automated: { dataType: 'boolean' }, - timestamp: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'datetime' }], - required: true, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MessageResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, }, - reviewer: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - email: { dataType: 'string', required: true }, - username: { dataType: 'string', required: true }, - }, - required: true, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateRepoBody": { + "dataType": "refObject", + "properties": { + "url": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "project": {"dataType":"string","required":true}, + "users": {"dataType":"nestedObjectLiteral","nestedProperties":{"canAuthorise":{"dataType":"array","array":{"dataType":"string"},"required":true},"canPush":{"dataType":"array","array":{"dataType":"string"},"required":true}}}, + "_id": {"dataType":"string"}, }, - }, - validators: {}, + "additionalProperties": false, }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AttestationAnswer: { - dataType: 'refObject', - properties: { - label: { dataType: 'string', required: true }, - checked: { dataType: 'boolean', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - CompletedAttestation: { - dataType: 'refAlias', - type: { - dataType: 'intersection', - subSchemas: [ - { ref: 'AttestationBase' }, - { - dataType: 'nestedObjectLiteral', - nestedProperties: { - answers: { - dataType: 'array', - array: { dataType: 'refObject', ref: 'AttestationAnswer' }, - required: true, - }, - }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UsernameBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, }, - ], - validators: {}, - }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - Rejection: { - dataType: 'refAlias', - type: { - dataType: 'intersection', - subSchemas: [ - { ref: 'AttestationBase' }, - { - dataType: 'nestedObjectLiteral', - nestedProperties: { reason: { dataType: 'string', required: true } }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Step": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "stepName": {"dataType":"string","required":true}, + "content": {"dataType":"any","required":true}, + "error": {"dataType":"boolean","required":true}, + "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "blocked": {"dataType":"boolean","required":true}, + "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "logs": {"dataType":"array","array":{"dataType":"string"},"default":[]}, }, - ], - validators: {}, - }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - Action: { - dataType: 'refObject', - properties: { - id: { dataType: 'string', required: true }, - type: { dataType: 'string', required: true }, - method: { dataType: 'string', required: true }, - timestamp: { dataType: 'double', required: true }, - project: { dataType: 'string', required: true }, - repoName: { dataType: 'string', required: true }, - url: { dataType: 'string', required: true }, - repo: { dataType: 'string', required: true }, - steps: { dataType: 'array', array: { dataType: 'refObject', ref: 'Step' }, default: [] }, - error: { dataType: 'boolean', default: false }, - errorMessage: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - }, - blocked: { dataType: 'boolean', default: false }, - blockedMessage: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - }, - allowPush: { dataType: 'boolean', default: false }, - authorised: { dataType: 'boolean', default: false }, - canceled: { dataType: 'boolean', default: false }, - rejected: { dataType: 'boolean', default: false }, - autoApproved: { dataType: 'boolean', default: false }, - autoRejected: { dataType: 'boolean', default: false }, - commitData: { - dataType: 'array', - array: { dataType: 'refAlias', ref: 'CommitData' }, - default: [], - }, - commitFrom: { dataType: 'string' }, - commitTo: { dataType: 'string' }, - branch: { dataType: 'string' }, - message: { dataType: 'string' }, - author: { dataType: 'string' }, - user: { dataType: 'string' }, - userEmail: { dataType: 'string' }, - attestation: { ref: 'CompletedAttestation' }, - rejection: { ref: 'Rejection' }, - lastStep: { ref: 'Step' }, - proxyGitPath: { dataType: 'string' }, - newIdxFiles: { dataType: 'array', array: { dataType: 'string' } }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - RejectBody: { - dataType: 'refObject', - properties: { - reason: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AuthoriseBody: { - dataType: 'refObject', - properties: { - params: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - attestation: { - dataType: 'array', - array: { dataType: 'refObject', ref: 'AttestationAnswer' }, - required: true, - }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CommitData": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true},"commitTimestamp":{"dataType":"string","required":true},"committerEmail":{"dataType":"string","required":true},"authorEmail":{"dataType":"string","required":true},"committer":{"dataType":"string","required":true},"author":{"dataType":"string","required":true},"parent":{"dataType":"string","required":true},"tree":{"dataType":"string","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationBase": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"automated":{"dataType":"boolean"},"timestamp":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"datetime"}],"required":true},"reviewer":{"dataType":"nestedObjectLiteral","nestedProperties":{"email":{"dataType":"string","required":true},"username":{"dataType":"string","required":true}},"required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationAnswer": { + "dataType": "refObject", + "properties": { + "label": {"dataType":"string","required":true}, + "checked": {"dataType":"boolean","required":true}, }, - required: true, - }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - ApiResources: { - dataType: 'refObject', - properties: { - healthcheck: { dataType: 'string', required: true }, - push: { dataType: 'string', required: true }, - auth: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - HealthResponse: { - dataType: 'refObject', - properties: { - message: { dataType: 'enum', enums: ['ok'], required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - Link: { - dataType: 'refObject', - properties: { - text: { dataType: 'string', required: true }, - url: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - QuestionTooltip: { - dataType: 'refObject', - properties: { - links: { dataType: 'array', array: { dataType: 'refObject', ref: 'Link' } }, - text: { dataType: 'string', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - Question: { - dataType: 'refObject', - properties: { - label: { dataType: 'string', required: true }, - tooltip: { ref: 'QuestionTooltip', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AttestationConfig: { - dataType: 'refObject', - properties: { - questions: { dataType: 'array', array: { dataType: 'refObject', ref: 'Question' } }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CompletedAttestation": { + "dataType": "refAlias", + "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"answers":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Rejection": { + "dataType": "refAlias", + "type": {"dataType":"intersection","subSchemas":[{"ref":"AttestationBase"},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string","required":true}}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Action": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "type": {"dataType":"string","required":true}, + "method": {"dataType":"string","required":true}, + "timestamp": {"dataType":"double","required":true}, + "project": {"dataType":"string","required":true}, + "repoName": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, + "repo": {"dataType":"string","required":true}, + "steps": {"dataType":"array","array":{"dataType":"refObject","ref":"Step"},"default":[]}, + "error": {"dataType":"boolean","default":false}, + "errorMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, + "blocked": {"dataType":"boolean","default":false}, + "blockedMessage": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}]}, + "allowPush": {"dataType":"boolean","default":false}, + "authorised": {"dataType":"boolean","default":false}, + "canceled": {"dataType":"boolean","default":false}, + "rejected": {"dataType":"boolean","default":false}, + "autoApproved": {"dataType":"boolean","default":false}, + "autoRejected": {"dataType":"boolean","default":false}, + "commitData": {"dataType":"array","array":{"dataType":"refAlias","ref":"CommitData"},"default":[]}, + "commitFrom": {"dataType":"string"}, + "commitTo": {"dataType":"string"}, + "branch": {"dataType":"string"}, + "message": {"dataType":"string"}, + "author": {"dataType":"string"}, + "user": {"dataType":"string"}, + "userEmail": {"dataType":"string"}, + "attestation": {"ref":"CompletedAttestation"}, + "rejection": {"ref":"Rejection"}, + "lastStep": {"ref":"Step"}, + "proxyGitPath": {"dataType":"string"}, + "newIdxFiles": {"dataType":"array","array":{"dataType":"string"}}, + }, + "additionalProperties": false, }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - RouteAuthRule: { - dataType: 'refObject', - properties: { - adminOnly: { dataType: 'boolean' }, - loginRequired: { dataType: 'boolean' }, - pattern: { dataType: 'string' }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RejectBody": { + "dataType": "refObject", + "properties": { + "reason": {"dataType":"string","required":true}, + }, + "additionalProperties": false, }, - additionalProperties: { dataType: 'any' }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - UIRouteAuth: { - dataType: 'refObject', - properties: { - enabled: { dataType: 'boolean' }, - rules: { dataType: 'array', array: { dataType: 'refObject', ref: 'RouteAuthRule' } }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthoriseBody": { + "dataType": "refObject", + "properties": { + "params": {"dataType":"nestedObjectLiteral","nestedProperties":{"attestation":{"dataType":"array","array":{"dataType":"refObject","ref":"AttestationAnswer"},"required":true}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResources": { + "dataType": "refObject", + "properties": { + "healthcheck": {"dataType":"string","required":true}, + "push": {"dataType":"string","required":true}, + "auth": {"dataType":"string","required":true}, + }, + "additionalProperties": false, }, - additionalProperties: { dataType: 'any' }, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AuthResources: { - dataType: 'refObject', - properties: { - login: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - uri: { dataType: 'string', required: true }, - action: { dataType: 'enum', enums: ['post'], required: true }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "HealthResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"enum","enums":["ok"],"required":true}, }, - required: true, - }, - profile: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - uri: { dataType: 'string', required: true }, - action: { dataType: 'enum', enums: ['get'], required: true }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Link": { + "dataType": "refObject", + "properties": { + "text": {"dataType":"string","required":true}, + "url": {"dataType":"string","required":true}, }, - required: true, - }, - logout: { - dataType: 'nestedObjectLiteral', - nestedProperties: { - uri: { dataType: 'string', required: true }, - action: { dataType: 'enum', enums: ['post'], required: true }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "QuestionTooltip": { + "dataType": "refObject", + "properties": { + "links": {"dataType":"array","array":{"dataType":"refObject","ref":"Link"}}, + "text": {"dataType":"string","required":true}, }, - required: true, - }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - AuthConfigResponse: { - dataType: 'refObject', - properties: { - usernamePasswordMethod: { - dataType: 'union', - subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], - required: true, - }, - otherMethods: { dataType: 'array', array: { dataType: 'string' }, required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - LoginResponse: { - dataType: 'refObject', - properties: { - message: { dataType: 'enum', enums: ['success'], required: true }, - user: { ref: 'PublicUser', required: true }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - 'Express.User': { - dataType: 'refObject', - properties: {}, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - LogoutResponse: { - dataType: 'refObject', - properties: { - isAuth: { dataType: 'boolean', required: true }, - user: { - dataType: 'union', - subSchemas: [{ ref: 'Express.User' }, { dataType: 'undefined' }], - required: true, - }, - }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - GitAccountBody: { - dataType: 'refObject', - properties: { - username: { dataType: 'string' }, - id: { dataType: 'string' }, - gitAccount: { dataType: 'string', required: true }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Question": { + "dataType": "refObject", + "properties": { + "label": {"dataType":"string","required":true}, + "tooltip": {"ref":"QuestionTooltip","required":true}, + }, + "additionalProperties": false, }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - CreateUserResponse: { - dataType: 'refObject', - properties: { - message: { dataType: 'string', required: true }, - username: { dataType: 'string', required: true }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AttestationConfig": { + "dataType": "refObject", + "properties": { + "questions": {"dataType":"array","array":{"dataType":"refObject","ref":"Question"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RouteAuthRule": { + "dataType": "refObject", + "properties": { + "adminOnly": {"dataType":"boolean"}, + "loginRequired": {"dataType":"boolean"}, + "pattern": {"dataType":"string"}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UIRouteAuth": { + "dataType": "refObject", + "properties": { + "enabled": {"dataType":"boolean"}, + "rules": {"dataType":"array","array":{"dataType":"refObject","ref":"RouteAuthRule"}}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthResources": { + "dataType": "refObject", + "properties": { + "login": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, + "profile": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["get"],"required":true}},"required":true}, + "logout": {"dataType":"nestedObjectLiteral","nestedProperties":{"uri":{"dataType":"string","required":true},"action":{"dataType":"enum","enums":["post"],"required":true}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthConfigResponse": { + "dataType": "refObject", + "properties": { + "usernamePasswordMethod": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "otherMethods": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LoginResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"enum","enums":["success"],"required":true}, + "user": {"ref":"PublicUser","required":true}, + }, + "additionalProperties": false, }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - CreateUserBody: { - dataType: 'refObject', - properties: { - username: { dataType: 'string', required: true }, - password: { dataType: 'string', required: true }, - email: { dataType: 'string', required: true }, - gitAccount: { dataType: 'string', required: true }, - admin: { dataType: 'boolean' }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Express.User": { + "dataType": "refObject", + "properties": { + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LogoutResponse": { + "dataType": "refObject", + "properties": { + "isAuth": {"dataType":"boolean","required":true}, + "user": {"dataType":"union","subSchemas":[{"ref":"Express.User"},{"dataType":"undefined"}],"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GitAccountBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string"}, + "id": {"dataType":"string"}, + "gitAccount": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateUserResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + "username": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateUserBody": { + "dataType": "refObject", + "properties": { + "username": {"dataType":"string","required":true}, + "password": {"dataType":"string","required":true}, + "email": {"dataType":"string","required":true}, + "gitAccount": {"dataType":"string","required":true}, + "admin": {"dataType":"boolean"}, + }, + "additionalProperties": false, }, - additionalProperties: false, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa }; -const templateService = new ExpressTemplateService(models, { - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, -}); +const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa -export function RegisterRoutes(app: Router) { - // ########################################################################################################### - // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look - // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa - // ########################################################################################################### - - const argsUserController_getUsers: Record = {}; - app.get( - '/api/v1/user', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(UserController), - ...fetchMiddlewares(UserController.prototype.getUsers), - - async function UserController_getUsers(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsUserController_getUsers, - request, - response, - }); - - const controller = new UserController(); - - await templateService.apiHandler({ - methodName: 'getUsers', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsUserController_getUser: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.get( - '/api/v1/user/:id', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(UserController), - ...fetchMiddlewares(UserController.prototype.getUser), - - async function UserController_getUser(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsUserController_getUser, - request, - response, - }); - - const controller = new UserController(); - - await templateService.apiHandler({ - methodName: 'getUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_getRepos: Record = { - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - }; - app.get( - '/api/v1/repo', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.getRepos), - - async function RepoController_getRepos(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_getRepos, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'getRepos', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_getRepo: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.get( - '/api/v1/repo/:id', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.getRepo), - - async function RepoController_getRepo(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_getRepo, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'getRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_createRepo: Record = { - body: { in: 'body', name: 'body', required: true, ref: 'CreateRepoBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - validationErrorResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - conflictResponse: { - in: 'res', - name: '409', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - internalServerErrorResponse: { - in: 'res', - name: '500', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/v1/repo', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.createRepo), - - async function RepoController_createRepo(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_createRepo, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'createRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_addPushUser: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - body: { in: 'body', name: 'body', required: true, ref: 'UsernameBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - userNotFoundResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { error: { dataType: 'string', required: true } }, - }, - }; - app.patch( - '/api/v1/repo/:id/user/push', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.addPushUser), - - async function RepoController_addPushUser(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_addPushUser, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'addPushUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_addAuthoriseUser: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - body: { in: 'body', name: 'body', required: true, ref: 'UsernameBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - userNotFoundResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { error: { dataType: 'string', required: true } }, - }, - }; - app.patch( - '/api/v1/repo/:id/user/authorise', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.addAuthoriseUser), - - async function RepoController_addAuthoriseUser( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_addAuthoriseUser, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'addAuthoriseUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_removeAuthoriseUser: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - username: { in: 'path', name: 'username', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - userNotFoundResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { error: { dataType: 'string', required: true } }, - }, - }; - app.delete( - '/api/v1/repo/:id/user/authorise/:username', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.removeAuthoriseUser), - - async function RepoController_removeAuthoriseUser( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_removeAuthoriseUser, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'removeAuthoriseUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_removePushUser: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - username: { in: 'path', name: 'username', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - userNotFoundResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { error: { dataType: 'string', required: true } }, - }, - }; - app.delete( - '/api/v1/repo/:id/user/push/:username', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.removePushUser), - - async function RepoController_removePushUser( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_removePushUser, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'removePushUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsRepoController_deleteRepo: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.delete( - '/api/v1/repo/:id/delete', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(RepoController), - ...fetchMiddlewares(RepoController.prototype.deleteRepo), - - async function RepoController_deleteRepo(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsRepoController_deleteRepo, - request, - response, - }); - - const controller = new RepoController(); - - await templateService.apiHandler({ - methodName: 'deleteRepo', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_getPushes: Record = { - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - }; - app.get( - '/api/v1/push', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(PushController), - ...fetchMiddlewares(PushController.prototype.getPushes), - - async function PushController_getPushes(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsPushController_getPushes, - request, - response, - }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'getPushes', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_getPush: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.get( - '/api/v1/push/:id', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(PushController), - ...fetchMiddlewares(PushController.prototype.getPush), - - async function PushController_getPush(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsPushController_getPush, - request, - response, - }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'getPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_rejectPush: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - body: { in: 'body', name: 'body', required: true, ref: 'RejectBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - validationErrorResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - forbiddenResponse: { - in: 'res', - name: '403', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/v1/push/:id/reject', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(PushController), - ...fetchMiddlewares(PushController.prototype.rejectPush), - - async function PushController_rejectPush(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsPushController_rejectPush, - request, - response, - }); - - const controller = new PushController(); - - await templateService.apiHandler({ - methodName: 'rejectPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_authorisePush: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - body: { in: 'body', name: 'body', required: true, ref: 'AuthoriseBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - validationErrorResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - forbiddenResponse: { - in: 'res', - name: '403', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/v1/push/:id/authorise', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(PushController), - ...fetchMiddlewares(PushController.prototype.authorisePush), - - async function PushController_authorisePush( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsPushController_authorisePush, - request, - response, - }); - const controller = new PushController(); - await templateService.apiHandler({ - methodName: 'authorisePush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, - }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsPushController_cancelPush: Record = { - id: { in: 'path', name: 'id', required: true, dataType: 'string' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - forbiddenResponse: { - in: 'res', - name: '403', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/v1/push/:id/cancel', - authenticateMiddleware([{ jwt: [] }]), - ...fetchMiddlewares(PushController), - ...fetchMiddlewares(PushController.prototype.cancelPush), - - async function PushController_cancelPush(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsPushController_cancelPush, - request, - response, - }); - const controller = new PushController(); +export function RegisterRoutes(app: Router) { - await templateService.apiHandler({ - methodName: 'cancelPush', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + + + + const argsUserController_getUsers: Record = { + }; + app.get('/api/v1/user', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.getUsers)), + + async function UserController_getUsers(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUsers, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getUsers', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsHomeController_getResources: Record = {}; - app.get( - '/api', - ...fetchMiddlewares(HomeController), - ...fetchMiddlewares(HomeController.prototype.getResources), - - async function HomeController_getResources( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsHomeController_getResources, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_getUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/user/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.getUser)), + + async function UserController_getUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getUser, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new HomeController(); - - await templateService.apiHandler({ - methodName: 'getResources', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepos: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/api/v1/repo', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.getRepos)), + + async function RepoController_getRepos(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepos, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepos', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsHealthController_check: Record = {}; - app.get( - '/api/v1/healthcheck', - ...fetchMiddlewares(HealthController), - ...fetchMiddlewares(HealthController.prototype.check), - - async function HealthController_check(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsHealthController_check, - request, - response, - }); - - const controller = new HealthController(); - - await templateService.apiHandler({ - methodName: 'check', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_getRepo: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/repo/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.getRepo)), + + async function RepoController_getRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_getRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'getRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getAttestation: Record = {}; - app.get( - '/api/v1/config/attestation', - ...fetchMiddlewares(ConfigController), - ...fetchMiddlewares(ConfigController.prototype.getAttestation), - - async function ConfigController_getAttestation( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsConfigController_getAttestation, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_createRepo: Record = { + body: {"in":"body","name":"body","required":true,"ref":"CreateRepoBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + conflictResponse: {"in":"res","name":"409","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/repo', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.createRepo)), + + async function RepoController_createRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_createRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'createRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getAttestation', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addPushUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.patch('/api/v1/repo/:id/user/push', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.addPushUser)), + + async function RepoController_addPushUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addPushUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addPushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getUrlShortener: Record = {}; - app.get( - '/api/v1/config/urlShortener', - ...fetchMiddlewares(ConfigController), - ...fetchMiddlewares(ConfigController.prototype.getUrlShortener), - - async function ConfigController_getUrlShortener( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsConfigController_getUrlShortener, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_addAuthoriseUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"UsernameBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.patch('/api/v1/repo/:id/user/authorise', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.addAuthoriseUser)), + + async function RepoController_addAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_addAuthoriseUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'addAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getUrlShortener', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removeAuthoriseUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/user/authorise/:username', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.removeAuthoriseUser)), + + async function RepoController_removeAuthoriseUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removeAuthoriseUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removeAuthoriseUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getContactEmail: Record = {}; - app.get( - '/api/v1/config/contactEmail', - ...fetchMiddlewares(ConfigController), - ...fetchMiddlewares(ConfigController.prototype.getContactEmail), - - async function ConfigController_getContactEmail( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsConfigController_getContactEmail, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_removePushUser: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + userNotFoundResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/user/push/:username', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.removePushUser)), + + async function RepoController_removePushUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_removePushUser, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'removePushUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getContactEmail', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsRepoController_deleteRepo: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/repo/:id/delete', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(RepoController)), + ...(fetchMiddlewares(RepoController.prototype.deleteRepo)), + + async function RepoController_deleteRepo(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsRepoController_deleteRepo, request, response }); + + const controller = new RepoController(); + + await templateService.apiHandler({ + methodName: 'deleteRepo', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsConfigController_getUiRouteAuth: Record = {}; - app.get( - '/api/v1/config/uiRouteAuth', - ...fetchMiddlewares(ConfigController), - ...fetchMiddlewares(ConfigController.prototype.getUiRouteAuth), - - async function ConfigController_getUiRouteAuth( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsConfigController_getUiRouteAuth, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPushes: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/api/v1/push', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.getPushes)), + + async function PushController_getPushes(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPushes, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPushes', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new ConfigController(); - - await templateService.apiHandler({ - methodName: 'getUiRouteAuth', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_getPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/push/:id', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.getPush)), + + async function PushController_getPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_getPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'getPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getResources: Record = {}; - app.get( - '/api/auth', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.getResources), - - async function AuthController_getResources( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_getResources, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_rejectPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"RejectBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/reject', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.rejectPush)), + + async function PushController_rejectPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_rejectPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'rejectPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getResources', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_authorisePush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"AuthoriseBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/authorise', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.authorisePush)), + + async function PushController_authorisePush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_authorisePush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'authorisePush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getAuthConfig: Record = {}; - app.get( - '/api/auth/config', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.getAuthConfig), - - async function AuthController_getAuthConfig( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_getAuthConfig, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsPushController_cancelPush: Record = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/push/:id/cancel', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(PushController)), + ...(fetchMiddlewares(PushController.prototype.cancelPush)), + + async function PushController_cancelPush(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsPushController_cancelPush, request, response }); + + const controller = new PushController(); + + await templateService.apiHandler({ + methodName: 'cancelPush', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getAuthConfig', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHomeController_getResources: Record = { + }; + app.get('/api', + ...(fetchMiddlewares(HomeController)), + ...(fetchMiddlewares(HomeController.prototype.getResources)), + + async function HomeController_getResources(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsHomeController_getResources, request, response }); + + const controller = new HomeController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_login: Record = { - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - }; - app.post( - '/api/auth/login', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.login), - - async function AuthController_login(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_login, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsHealthController_check: Record = { + }; + app.get('/api/v1/healthcheck', + ...(fetchMiddlewares(HealthController)), + ...(fetchMiddlewares(HealthController.prototype.check)), + + async function HealthController_check(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsHealthController_check, request, response }); + + const controller = new HealthController(); + + await templateService.apiHandler({ + methodName: 'check', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'login', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getAttestation: Record = { + }; + app.get('/api/v1/config/attestation', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getAttestation)), + + async function ConfigController_getAttestation(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getAttestation, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getAttestation', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_initiateOIDC: Record = {}; - app.get( - '/api/auth/openidconnect', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.initiateOIDC), - - async function AuthController_initiateOIDC( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_initiateOIDC, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUrlShortener: Record = { + }; + app.get('/api/v1/config/urlShortener', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getUrlShortener)), + + async function ConfigController_getUrlShortener(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUrlShortener, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUrlShortener', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'initiateOIDC', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getContactEmail: Record = { + }; + app.get('/api/v1/config/contactEmail', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getContactEmail)), + + async function ConfigController_getContactEmail(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getContactEmail, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getContactEmail', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_handleOIDCCallback: Record = {}; - app.get( - '/api/auth/openidconnect/callback', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.handleOIDCCallback), - - async function AuthController_handleOIDCCallback( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_handleOIDCCallback, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getUiRouteAuth: Record = { + }; + app.get('/api/v1/config/uiRouteAuth', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getUiRouteAuth)), + + async function ConfigController_getUiRouteAuth(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getUiRouteAuth, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getUiRouteAuth', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'handleOIDCCallback', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getResources: Record = { + }; + app.get('/api/auth', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getResources)), + + async function AuthController_getResources(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getResources, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getResources', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_logout: Record = { - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - }; - app.post( - '/api/auth/logout', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.logout), - - async function AuthController_logout(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_logout, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getAuthConfig: Record = { + }; + app.get('/api/auth/config', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getAuthConfig)), + + async function AuthController_getAuthConfig(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getAuthConfig, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getAuthConfig', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'logout', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_login: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.post('/api/auth/login', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.login)), + + async function AuthController_login(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_login, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'login', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_getProfile: Record = { - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.get( - '/api/auth/profile', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.getProfile), - - async function AuthController_getProfile(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_getProfile, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_initiateOIDC: Record = { + }; + app.get('/api/auth/openidconnect', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.initiateOIDC)), + + async function AuthController_initiateOIDC(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_initiateOIDC, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'initiateOIDC', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'getProfile', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_handleOIDCCallback: Record = { + }; + app.get('/api/auth/openidconnect/callback', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.handleOIDCCallback)), + + async function AuthController_handleOIDCCallback(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_handleOIDCCallback, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'handleOIDCCallback', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_updateGitAccount: Record = { - body: { in: 'body', name: 'body', required: true, ref: 'GitAccountBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '401', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - notFoundResponse: { - in: 'res', - name: '404', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - validationErrorResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - forbiddenResponse: { - in: 'res', - name: '403', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - internalServerErrorResponse: { - in: 'res', - name: '500', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/auth/gitAccount', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.updateGitAccount), - - async function AuthController_updateGitAccount( - request: ExRequest, - response: ExResponse, - next: any, - ) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_updateGitAccount, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_logout: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.post('/api/auth/logout', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.logout)), + + async function AuthController_logout(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_logout, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'logout', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'updateGitAccount', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getProfile: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.get('/api/auth/profile', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getProfile)), + + async function AuthController_getProfile(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getProfile, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getProfile', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - const argsAuthController_createUser: Record = { - body: { in: 'body', name: 'body', required: true, ref: 'CreateUserBody' }, - req: { in: 'request', name: 'req', required: true, dataType: 'object' }, - unauthorisedResponse: { - in: 'res', - name: '403', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - validationErrorResponse: { - in: 'res', - name: '400', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - internalServerErrorResponse: { - in: 'res', - name: '500', - required: true, - dataType: 'nestedObjectLiteral', - nestedProperties: { message: { dataType: 'string', required: true } }, - }, - }; - app.post( - '/api/auth/create-user', - ...fetchMiddlewares(AuthController), - ...fetchMiddlewares(AuthController.prototype.createUser), - - async function AuthController_createUser(request: ExRequest, response: ExResponse, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - let validatedArgs: any[] = []; - try { - validatedArgs = templateService.getValidatedArgs({ - args: argsAuthController_createUser, - request, - response, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_updateGitAccount: Record = { + body: {"in":"body","name":"body","required":true,"ref":"GitAccountBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/auth/gitAccount', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.updateGitAccount)), + + async function AuthController_updateGitAccount(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_updateGitAccount, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'updateGitAccount', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - - const controller = new AuthController(); - - await templateService.apiHandler({ - methodName: 'createUser', - controller, - response, - next, - validatedArgs, - successStatus: undefined, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_createUser: Record = { + body: {"in":"body","name":"body","required":true,"ref":"CreateUserBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + unauthorisedResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + validationErrorResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + internalServerErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"message":{"dataType":"string","required":true}}}, + }; + app.post('/api/auth/create-user', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.createUser)), + + async function AuthController_createUser(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_createUser, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'createUser', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } }); - } catch (err) { - return next(err); - } - }, - ); - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - function authenticateMiddleware(security: TsoaRoute.Security[] = []) { - return async function runAuthenticationMiddleware(request: any, response: any, next: any) { - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - // keep track of failed auth attempts so we can hand back the most - // recent one. This behavior was previously existing so preserving it - // here - const failedAttempts: any[] = []; - const pushAndRethrow = (error: any) => { - failedAttempts.push(error); - throw error; - }; - - const secMethodOrPromises: Promise[] = []; - for (const secMethod of security) { - if (Object.keys(secMethod).length > 1) { - const secMethodAndPromises: Promise[] = []; - - for (const name in secMethod) { - secMethodAndPromises.push( - expressAuthenticationRecasted(request, name, secMethod[name], response).catch( - pushAndRethrow, - ), - ); - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - secMethodOrPromises.push( - Promise.all(secMethodAndPromises).then((users) => { - return users[0]; - }), - ); - } else { - for (const name in secMethod) { - secMethodOrPromises.push( - expressAuthenticationRecasted(request, name, secMethod[name], response).catch( - pushAndRethrow, - ), - ); - } - } - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - - try { - request['user'] = await Promise.any(secMethodOrPromises); - - // Response was sent in middleware, abort - if (response.writableEnded) { - return; - } - - next(); - } catch (err) { - // Show most recent error as response - const error = failedAttempts.pop(); - error.status = error.status || 401; - - // Response was sent in middleware, abort - if (response.writableEnded) { - return; + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return async function runAuthenticationMiddleware(request: any, response: any, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // keep track of failed auth attempts so we can hand back the most + // recent one. This behavior was previously existing so preserving it + // here + const failedAttempts: any[] = []; + const pushAndRethrow = (error: any) => { + failedAttempts.push(error); + throw error; + }; + + const secMethodOrPromises: Promise[] = []; + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + const secMethodAndPromises: Promise[] = []; + + for (const name in secMethod) { + secMethodAndPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response) + .catch(pushAndRethrow) + ); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + secMethodOrPromises.push(Promise.all(secMethodAndPromises) + .then(users => { return users[0]; })); + } else { + for (const name in secMethod) { + secMethodOrPromises.push( + expressAuthenticationRecasted(request, name, secMethod[name], response) + .catch(pushAndRethrow) + ); + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + try { + request['user'] = await Promise.any(secMethodOrPromises); + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; + } + + next(); + } + catch(err) { + // Show most recent error as response + const error = failedAttempts.pop(); + error.status = error.status || 401; + + // Response was sent in middleware, abort + if (response.writableEnded) { + return; + } + next(error); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa } - next(error); - } - - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - }; - } + } - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa } // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa From 36cb793b46e1a828e93af6998f2d358b317bafe4 Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Tue, 9 Jun 2026 04:04:36 +0200 Subject: [PATCH 17/18] feat(pre-commit): add generaton of swagger docs on commit --- scripts/check-tsoa-routes.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/scripts/check-tsoa-routes.js b/scripts/check-tsoa-routes.js index 277b1cb18..acb3b979d 100644 --- a/scripts/check-tsoa-routes.js +++ b/scripts/check-tsoa-routes.js @@ -18,22 +18,21 @@ /* * Invoked by lint-staged when any TSOA input is staged. Regenerates - * src/service/generatedRoutes.ts via `npm run build-tsoa` and stages it - * so the commit always includes routes in sync with their inputs — - * the same fix-then-continue behaviour as `prettier --write`. + * src/service/generatedRoutes.ts via `npm run build-tsoa` and the API + * docs under website/docs/api/ via `npm run gen-swagger-doc`, then stages + * both so the commit always includes routes and docs in sync with their + * inputs — the same fix-then-continue behaviour as `prettier --write`. */ const { spawnSync } = require('node:child_process'); -const build = spawnSync('npm run --silent build-tsoa', { - stdio: 'inherit', - shell: true, -}); -if (build.status !== 0) { - process.exit(build.status ?? 1); -} +const run = (command) => { + const result = spawnSync(command, { stdio: 'inherit', shell: true }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +}; -const add = spawnSync('git add src/service/generatedRoutes.ts', { - stdio: 'inherit', - shell: true, -}); -process.exit(add.status ?? 0); +run('npm run --silent build-tsoa'); +run('git add src/service/generatedRoutes.ts'); +run('npm run --silent gen-swagger-doc'); +run('git add website/docs/api'); From 7ba58da0c389fd632d3347a6ae95e3d335106f3e Mon Sep 17 00:00:00 2001 From: Andrii Kotliar Date: Mon, 15 Jun 2026 22:46:38 +0200 Subject: [PATCH 18/18] fix(service): allign tsoa with ssh routes changes, align new tests to tsoa and disable formatting --- package.json | 4 +- src/service/controllers/AuthController.ts | 11 + src/service/controllers/ConfigController.ts | 7 +- src/service/controllers/UserController.ts | 206 +++++++++++++++- src/service/decorators/response.types.ts | 8 + src/service/generatedRoutes.ts | 238 ++++++++++++++++++ src/service/interfaces/auth.interfaces.ts | 4 + src/service/interfaces/user.interfaces.ts | 37 +++ test/fixtures/test-package/package-lock.json | 91 +++---- test/services/routes/users.test.ts | 241 +++++-------------- website/docs/api/auth.mdx | 14 +- website/docs/api/config.mdx | 10 +- website/docs/api/index.mdx | 10 +- website/docs/api/repositories.mdx | 2 + website/docs/api/users.mdx | 86 ++++++- 15 files changed, 737 insertions(+), 232 deletions(-) create mode 100644 src/service/interfaces/user.interfaces.ts diff --git a/package.json b/package.json index 4c8d09262..4f465bd1b 100644 --- a/package.json +++ b/package.json @@ -76,8 +76,8 @@ "prepare": "node ./scripts/prepare.js", "lint": "eslint", "lint:fix": "eslint --fix", - "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --config ./.prettierrc", - "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --config ./.prettierrc", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --ignore-path .prettierignore --config ./.prettierrc", + "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml,css,scss}\" --ignore-path .gitignore --ignore-path .prettierignore --config ./.prettierrc", "migrate:urls": "node scripts/migrate/migrate-urls.js", "migrate:users": "node scripts/migrate/migrate-users.js", "backup:urls": "node scripts/migrate/backup-urls.js", diff --git a/src/service/controllers/AuthController.ts b/src/service/controllers/AuthController.ts index f124e7e96..4e7254971 100644 --- a/src/service/controllers/AuthController.ts +++ b/src/service/controllers/AuthController.ts @@ -32,6 +32,7 @@ import { LoginResponse, LogoutResponse, CreateUserResponse, + CsrfTokenResponse, GitAccountBody, CreateUserBody, } from '../interfaces/auth.interfaces'; @@ -147,6 +148,16 @@ export class AuthController extends Controller { }; } + /** + * Returns a CSRF token for the current session, to be sent with subsequent + * state-changing requests. + */ + @Get('/csrf-token') + public getCsrfToken(@Request() req: ExpressRequest): CsrfTokenResponse { + console.log('req.user', req.user); + return { csrfToken: (req as ExpressRequest & { csrfToken: () => string }).csrfToken() }; + } + /** * Authenticates the user with a username/password strategy. * The appropriate passport strategy is selected dynamically based on configuration. diff --git a/src/service/controllers/ConfigController.ts b/src/service/controllers/ConfigController.ts index 6b7e6a854..8c0eb7abb 100644 --- a/src/service/controllers/ConfigController.ts +++ b/src/service/controllers/ConfigController.ts @@ -16,7 +16,7 @@ import { Controller, Get, Route, Tags } from 'tsoa'; import * as config from '../../config'; -import type { AttestationConfig, UIRouteAuth } from '../../config/generated/config'; +import type { AttestationConfig, SSH, UIRouteAuth } from '../../config/generated/config'; /** * Public configuration endpoints consumed by the UI. @@ -43,4 +43,9 @@ export class ConfigController extends Controller { public getUiRouteAuth(): UIRouteAuth { return config.getUIRouteAuth(); } + + @Get('/ssh') + public getSSH(): SSH { + return config.getSSHConfig(); + } } diff --git a/src/service/controllers/UserController.ts b/src/service/controllers/UserController.ts index 82e596b59..706ead87e 100644 --- a/src/service/controllers/UserController.ts +++ b/src/service/controllers/UserController.ts @@ -14,11 +14,57 @@ * limitations under the License. */ -import { Controller, Get, Path, Res, Route, Security, Tags } from 'tsoa'; +import { + Body, + Controller, + Delete, + Get, + Path, + Post, + Request, + Res, + Route, + Security, + Tags, +} from 'tsoa'; +import type { Request as ExpressRequest } from 'express'; +import crypto from 'crypto'; import * as db from '../../db'; import { PublicUser } from '../../db/types'; import { toPublicUser } from '../routes/utils'; -import { NotFoundResponse } from '../decorators/response.types'; +import { + AuthenticationRequiredResponse, + BadRequestErrorResponse, + ConflictErrorResponse, + ForbiddenErrorResponse, + NotFoundErrorResponse, + NotFoundResponse, + ServerErrorResponse, +} from '../decorators/response.types'; +import { + AddSSHKeyBody, + AddSSHKeyResponse, + RemoveSSHKeyResponse, + SSHKeyFingerprint, +} from '../interfaces/user.interfaces'; + +// Calculate SHA-256 fingerprint from SSH public key +// Note: This function is duplicated in src/cli/ssh-key.ts to keep CLI and server independent +function calculateFingerprint(publicKeyStr: string): string | null { + try { + const { utils } = require('ssh2'); + const parsed = utils.parseKey(publicKeyStr); + if (!parsed || parsed instanceof Error) { + return null; + } + const pubKey = parsed.getPublicSSH(); + const hash = crypto.createHash('sha256').update(pubKey).digest('base64'); + return `SHA256:${hash}`; + } catch (err) { + console.error('Error calculating fingerprint:', err); + return null; + } +} /** * User listing. @@ -53,4 +99,160 @@ export class UserController extends Controller { } return toPublicUser(user); } + + /** + * Returns the SSH key fingerprints for a user. + * Users may view their own keys; admins may view any user's keys. + */ + @Get('/{username}/ssh-key-fingerprints') + public async getSshKeyFingerprints( + @Path() username: string, + @Request() req: ExpressRequest, + @Res() authRequiredResponse: AuthenticationRequiredResponse, + @Res() forbiddenResponse: ForbiddenErrorResponse, + @Res() serverErrorResponse: ServerErrorResponse, + ): Promise { + if (!req.user) { + return authRequiredResponse(401, { error: 'Authentication required' }); + } + + const { username: requesterUsername, admin } = req.user as { + username: string; + admin: boolean; + }; + const targetUsername = username.toLowerCase(); + + // Only allow users to view their own keys, or admins to view any keys + if (requesterUsername !== targetUsername && !admin) { + return forbiddenResponse(403, { error: 'Not authorized to view keys for this user' }); + } + + try { + const publicKeys = await db.getPublicKeys(targetUsername); + return publicKeys.map((keyRecord) => ({ + fingerprint: keyRecord.fingerprint, + name: keyRecord.name, + addedAt: keyRecord.addedAt, + })); + } catch (error) { + console.error('Error retrieving SSH keys:', error); + return serverErrorResponse(500, { error: 'Failed to retrieve SSH keys' }); + } + } + + /** + * Adds an SSH public key to a user's account. + * Users may add keys to their own account; admins may add to any account. + */ + @Post('/{username}/ssh-keys') + public async addSshKey( + @Path() username: string, + @Body() body: AddSSHKeyBody, + @Request() req: ExpressRequest, + @Res() authRequiredResponse: AuthenticationRequiredResponse, + @Res() forbiddenResponse: ForbiddenErrorResponse, + @Res() badRequestResponse: BadRequestErrorResponse, + @Res() notFoundResponse: NotFoundErrorResponse, + @Res() conflictResponse: ConflictErrorResponse, + @Res() serverErrorResponse: ServerErrorResponse, + ): Promise { + if (!req.user) { + return authRequiredResponse(401, { error: 'Authentication required' }); + } + + const { username: requesterUsername, admin } = req.user as { + username: string; + admin: boolean; + }; + const targetUsername = username.toLowerCase(); + + // Only allow users to add keys to their own account, or admins to add to any account + if (requesterUsername !== targetUsername && !admin) { + return forbiddenResponse(403, { error: 'Not authorized to add keys for this user' }); + } + + const { publicKey, name } = body; + if (!publicKey) { + return badRequestResponse(400, { error: 'Public key is required' }); + } + + // Strip the comment from the key (everything after the last space) + const keyWithoutComment = publicKey.trim().split(' ').slice(0, 2).join(' '); + + // Calculate fingerprint + const fingerprint = calculateFingerprint(keyWithoutComment); + if (!fingerprint) { + return badRequestResponse(400, { error: 'Invalid SSH public key format' }); + } + + const publicKeyRecord = { + key: keyWithoutComment, + name: name || 'Unnamed Key', + addedAt: new Date().toISOString(), + fingerprint: fingerprint, + }; + + console.log('Adding SSH key', { targetUsername, fingerprint }); + try { + await db.addPublicKey(targetUsername, publicKeyRecord); + this.setStatus(201); + return { message: 'SSH key added successfully', fingerprint }; + } catch (error: unknown) { + console.error('Error adding SSH key:', error); + + // Return specific error message + const message = error instanceof Error ? error.message : undefined; + if (message === 'SSH key already exists') { + return conflictResponse(409, { error: 'This SSH key already exists' }); + } else if (message === 'User not found') { + return notFoundResponse(404, { error: 'User not found' }); + } + return serverErrorResponse(500, { error: message || 'Failed to add SSH key' }); + } + } + + /** + * Removes an SSH public key from a user's account by fingerprint. + * Users may remove keys from their own account; admins may remove from any account. + */ + @Delete('/{username}/ssh-keys/{fingerprint}') + public async removeSshKey( + @Path() username: string, + @Path() fingerprint: string, + @Request() req: ExpressRequest, + @Res() authRequiredResponse: AuthenticationRequiredResponse, + @Res() forbiddenResponse: ForbiddenErrorResponse, + @Res() notFoundResponse: NotFoundErrorResponse, + @Res() serverErrorResponse: ServerErrorResponse, + ): Promise { + if (!req.user) { + return authRequiredResponse(401, { error: 'Authentication required' }); + } + + const { username: requesterUsername, admin } = req.user as { + username: string; + admin: boolean; + }; + const targetUsername = username.toLowerCase(); + + // Only allow users to remove keys from their own account, or admins to remove from any account + if (requesterUsername !== targetUsername && !admin) { + return forbiddenResponse(403, { error: 'Not authorized to remove keys for this user' }); + } + + console.log('Removing SSH key', { targetUsername, fingerprint }); + try { + await db.removePublicKey(targetUsername, fingerprint); + return { message: 'SSH key removed successfully' }; + } catch (error: unknown) { + console.error('Error removing SSH key:', error); + + // Return specific error message + const message = error instanceof Error ? error.message : undefined; + if (message === 'User not found') { + return notFoundResponse(404, { error: 'User not found' }); + } + return serverErrorResponse(500, { error: message || 'Failed to remove SSH key' }); + } + } } diff --git a/src/service/decorators/response.types.ts b/src/service/decorators/response.types.ts index 478a145dd..9859aa6de 100644 --- a/src/service/decorators/response.types.ts +++ b/src/service/decorators/response.types.ts @@ -23,3 +23,11 @@ export type ValidationErrorResponse = TsoaResponse<400, { message: string }>; export type ConflictResponse = TsoaResponse<409, { message: string }>; export type InternalServerErrorResponse = TsoaResponse<500, { message: string }>; export type UserNotFoundResponse = TsoaResponse<400, { error: string }>; + +// Error responses using an `{ error: string }` body shape (consumed by the SSH key UI/CLI). +export type AuthenticationRequiredResponse = TsoaResponse<401, { error: string }>; +export type ForbiddenErrorResponse = TsoaResponse<403, { error: string }>; +export type BadRequestErrorResponse = TsoaResponse<400, { error: string }>; +export type NotFoundErrorResponse = TsoaResponse<404, { error: string }>; +export type ConflictErrorResponse = TsoaResponse<409, { error: string }>; +export type ServerErrorResponse = TsoaResponse<500, { error: string }>; diff --git a/src/service/generatedRoutes.ts b/src/service/generatedRoutes.ts index 588635686..870a523e9 100644 --- a/src/service/generatedRoutes.ts +++ b/src/service/generatedRoutes.ts @@ -40,6 +40,42 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SSHKeyFingerprint": { + "dataType": "refObject", + "properties": { + "fingerprint": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "addedAt": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AddSSHKeyResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + "fingerprint": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AddSSHKeyBody": { + "dataType": "refObject", + "properties": { + "publicKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RemoveSSHKeyResponse": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "RepoWithProxy": { "dataType": "refObject", "properties": { @@ -160,6 +196,8 @@ const models: TsoaRoute.Models = { "lastStep": {"ref":"Step"}, "proxyGitPath": {"dataType":"string"}, "newIdxFiles": {"dataType":"array","array":{"dataType":"string"}}, + "protocol": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["https"]},{"dataType":"enum","enums":["ssh"]}]}, + "pullAuthStrategy": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["basic"]},{"dataType":"enum","enums":["ssh-user-key"]},{"dataType":"enum","enums":["ssh-service-token"]},{"dataType":"enum","enums":["ssh-agent-forwarding"]},{"dataType":"enum","enums":["anonymous"]}]}, }, "additionalProperties": false, }, @@ -252,6 +290,28 @@ const models: TsoaRoute.Models = { "additionalProperties": {"dataType":"any"}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "HostKey": { + "dataType": "refObject", + "properties": { + "privateKeyPath": {"dataType":"string","required":true}, + "publicKeyPath": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SSH": { + "dataType": "refObject", + "properties": { + "agentForwardingErrorMessage": {"dataType":"string"}, + "debug": {"dataType":"boolean"}, + "enabled": {"dataType":"boolean","required":true}, + "hostKey": {"ref":"HostKey"}, + "knownHosts": {"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"string"}}, + "port": {"dataType":"double"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AuthResources": { "dataType": "refObject", "properties": { @@ -271,6 +331,14 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CsrfTokenResponse": { + "dataType": "refObject", + "properties": { + "csrfToken": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "LoginResponse": { "dataType": "refObject", "properties": { @@ -406,6 +474,117 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_getSshKeyFingerprints: Record = { + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + authRequiredResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + serverErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.get('/api/v1/user/:username/ssh-key-fingerprints', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.getSshKeyFingerprints)), + + async function UserController_getSshKeyFingerprints(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_getSshKeyFingerprints, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'getSshKeyFingerprints', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_addSshKey: Record = { + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"AddSSHKeyBody"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + authRequiredResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + badRequestResponse: {"in":"res","name":"400","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + conflictResponse: {"in":"res","name":"409","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + serverErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.post('/api/v1/user/:username/ssh-keys', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.addSshKey)), + + async function UserController_addSshKey(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_addSshKey, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'addSshKey', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_removeSshKey: Record = { + username: {"in":"path","name":"username","required":true,"dataType":"string"}, + fingerprint: {"in":"path","name":"fingerprint","required":true,"dataType":"string"}, + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + authRequiredResponse: {"in":"res","name":"401","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + forbiddenResponse: {"in":"res","name":"403","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + notFoundResponse: {"in":"res","name":"404","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + serverErrorResponse: {"in":"res","name":"500","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"error":{"dataType":"string","required":true}}}, + }; + app.delete('/api/v1/user/:username/ssh-keys/:fingerprint', + authenticateMiddleware([{"jwt":[]}]), + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.removeSshKey)), + + async function UserController_removeSshKey(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_removeSshKey, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'removeSshKey', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsRepoController_getRepos: Record = { req: {"in":"request","name":"req","required":true,"dataType":"object"}, }; @@ -1024,6 +1203,35 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsConfigController_getSSH: Record = { + }; + app.get('/api/v1/config/ssh', + ...(fetchMiddlewares(ConfigController)), + ...(fetchMiddlewares(ConfigController.prototype.getSSH)), + + async function ConfigController_getSSH(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsConfigController_getSSH, request, response }); + + const controller = new ConfigController(); + + await templateService.apiHandler({ + methodName: 'getSSH', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsAuthController_getResources: Record = { }; app.get('/api/auth', @@ -1082,6 +1290,36 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getCsrfToken: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/api/auth/csrf-token', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getCsrfToken)), + + async function AuthController_getCsrfToken(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getCsrfToken, request, response }); + + const controller = new AuthController(); + + await templateService.apiHandler({ + methodName: 'getCsrfToken', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsAuthController_login: Record = { req: {"in":"request","name":"req","required":true,"dataType":"object"}, }; diff --git a/src/service/interfaces/auth.interfaces.ts b/src/service/interfaces/auth.interfaces.ts index 6a00f490f..f1d1a79ef 100644 --- a/src/service/interfaces/auth.interfaces.ts +++ b/src/service/interfaces/auth.interfaces.ts @@ -42,6 +42,10 @@ export interface CreateUserResponse { username: string; } +export interface CsrfTokenResponse { + csrfToken: string; +} + export interface GitAccountBody { username?: string; id?: string; diff --git a/src/service/interfaces/user.interfaces.ts b/src/service/interfaces/user.interfaces.ts new file mode 100644 index 000000000..8df34469b --- /dev/null +++ b/src/service/interfaces/user.interfaces.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2026 GitProxy Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface SSHKeyFingerprint { + fingerprint: string; + name: string; + addedAt: string; +} + +export interface AddSSHKeyBody { + // Optional so the controller performs its own validation and returns a custom + // `{ error: 'Public key is required' }` response rather than tsoa's ValidateError. + publicKey?: string; + name?: string; +} + +export interface AddSSHKeyResponse { + message: string; + fingerprint: string; +} + +export interface RemoveSSHKeyResponse { + message: string; +} diff --git a/test/fixtures/test-package/package-lock.json b/test/fixtures/test-package/package-lock.json index cc9cabe8f..121563add 100644 --- a/test/fixtures/test-package/package-lock.json +++ b/test/fixtures/test-package/package-lock.json @@ -13,36 +13,40 @@ }, "../../..": { "name": "@finos/git-proxy", - "version": "2.0.0-rc.3", + "version": "2.0.0", "license": "Apache-2.0", "workspaces": [ "./packages/git-proxy-cli" ], "dependencies": { - "@aws-sdk/credential-providers": "^3.940.0", + "@aws-sdk/credential-providers": "^3.980.0", + "@fontsource/roboto": "^5.2.9", "@material-ui/core": "^4.12.4", "@material-ui/icons": "4.11.3", - "@primer/octicons-react": "^19.21.0", + "@primer/octicons-react": "^19.21.2", "@seald-io/nedb": "^4.1.2", - "axios": "^1.13.2", + "axios": "^1.13.4", "bcryptjs": "^3.0.3", "clsx": "^2.1.1", "concurrently": "^9.2.1", "connect-mongo": "^5.1.0", - "cors": "^2.8.5", - "diff2html": "^3.4.52", - "env-paths": "^3.0.0", + "cors": "^2.8.6", + "diff2html": "^3.4.56", + "env-paths": "^4.0.0", "escape-string-regexp": "^5.0.0", - "express": "^5.1.0", + "express": "^5.2.1", "express-http-proxy": "^2.1.2", "express-rate-limit": "^8.2.1", - "express-session": "^1.18.2", + "express-session": "^1.19.0", + "font-awesome": "^4.7.0", "history": "5.3.0", - "isomorphic-git": "^1.35.0", - "jsonwebtoken": "^9.0.2", + "https-proxy-agent": "^7.0.6", + "isomorphic-git": "^1.36.3", + "jsonwebtoken": "^9.0.3", "load-plugin": "^6.0.3", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lusca": "^1.7.0", + "material-design-icons": "^3.0.1", "moment": "^2.30.1", "mongodb": "^5.9.2", "openid-client": "^6.8.1", @@ -51,15 +55,15 @@ "passport-activedirectory": "^1.4.0", "passport-local": "^1.0.0", "perfect-scrollbar": "^1.5.6", - "prop-types": "15.8.1", "react": "^16.14.0", "react-dom": "^16.14.0", "react-html-parser": "^2.0.2", - "react-router-dom": "6.30.2", + "react-router-dom": "6.30.3", "simple-git": "^3.30.0", - "ssh2": "^1.17.0", - "uuid": "^11.1.0", - "validator": "^13.15.23", + "ssh2": "~1.17.0", + "tsoa": "^7.0.0-alpha.0", + "uuid": "^13.0.0", + "validator": "^13.15.26", "yargs": "^17.7.2" }, "bin": { @@ -67,62 +71,65 @@ "git-proxy-all": "concurrently 'npm run server' 'npm run client'" }, "devDependencies": { - "@babel/core": "^7.28.5", + "@babel/core": "^7.28.6", "@babel/preset-react": "^7.28.5", "@commitlint/cli": "^19.8.1", "@commitlint/config-conventional": "^19.8.1", - "@eslint/compat": "^2.0.0", - "@eslint/js": "^9.39.1", - "@eslint/json": "^0.14.0", + "@eslint/compat": "^2.0.2", + "@eslint/js": "^9.39.2", + "@eslint/json": "^1.0.1", "@types/activedirectory2": "^1.2.6", "@types/cors": "^2.8.19", "@types/domutils": "^2.1.0", - "@types/express": "^5.0.5", + "@types/express": "^5.0.6", "@types/express-http-proxy": "^1.6.7", "@types/express-session": "^1.18.2", "@types/jsonwebtoken": "^9.0.10", - "@types/lodash": "^4.17.20", + "@types/lodash": "^4.17.23", "@types/lusca": "^1.7.5", - "@types/node": "^22.19.1", + "@types/node": "^22.19.7", "@types/passport": "^1.0.17", "@types/passport-local": "^1.0.38", "@types/react-dom": "^17.0.26", "@types/react-html-parser": "^2.0.7", "@types/ssh2": "^1.15.5", "@types/supertest": "^6.0.3", - "@types/validator": "^13.15.9", + "@types/validator": "^13.15.10", "@types/yargs": "^17.0.35", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^3.2.4", - "cypress": "^15.6.0", - "eslint": "^9.39.1", + "c8": "^11.0.0", + "cross-env": "^10.1.0", + "cypress": "^15.9.0", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-cypress": "^5.2.0", + "eslint-plugin-cypress": "^5.2.1", + "eslint-plugin-license-header": "^0.9.0", "eslint-plugin-react": "^7.37.5", - "fast-check": "^4.3.0", - "globals": "^16.5.0", + "fast-check": "^4.5.3", + "globals": "^17.6.0", "husky": "^9.1.7", - "lint-staged": "^16.2.6", + "lint-staged": "^17.0.5", "nyc": "^17.1.0", - "prettier": "^3.6.2", + "prettier": "^3.8.1", "quicktype": "^23.2.6", - "supertest": "^7.1.4", + "supertest": "^7.2.2", "ts-node": "^10.9.2", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.4", - "vite": "^7.1.9", + "typescript-eslint": "^8.54.0", + "vite": "^7.3.1", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4" }, "engines": { - "node": ">=20.19.2" + "node": ">=22.13.1 || >=24.0.0" }, "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.27.0", - "@esbuild/darwin-x64": "^0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/win32-x64": "0.27.0" + "@esbuild/darwin-arm64": "^0.27.2", + "@esbuild/darwin-x64": "^0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/@finos/git-proxy": { diff --git a/test/services/routes/users.test.ts b/test/services/routes/users.test.ts index 7deeb93f5..eaedfd5e6 100644 --- a/test/services/routes/users.test.ts +++ b/test/services/routes/users.test.ts @@ -24,12 +24,22 @@ import crypto from 'crypto'; describe('Users API', () => { let app: Express; + // The authenticated user for the current request. Tests reassign this to + // exercise different users; set it to undefined for the unauthenticated case. + let currentUser: { username: string; admin?: boolean } | undefined; beforeEach(() => { + currentUser = { username: 'testuser' }; app = express(); app.use(express.json()); app.use((req, _res, next) => { - req.user = { username: 'testuser' }; + // tsoa's @Security('jwt') middleware overwrites req.user with whatever + // expressAuthentication returns. With a session (isAuthenticated() === true) + // it returns the existing req.user, so mark the request as session-authenticated. + if (currentUser) { + req.user = currentUser; + (req as any).isAuthenticated = () => true; + } next(); }); RegisterRoutes(app); @@ -110,39 +120,26 @@ describe('Users API', () => { vi.spyOn(db, 'removePublicKey').mockResolvedValue(undefined); }); - describe('GET /users/:username/ssh-key-fingerprints', () => { + describe('GET /api/v1/user/:username/ssh-key-fingerprints', () => { it('should return 401 when not authenticated', async () => { - const res = await request(app).get('/users/alice/ssh-key-fingerprints'); + currentUser = undefined; + const res = await request(app).get('/api/v1/user/alice/ssh-key-fingerprints'); expect(res.status).toBe(401); expect(res.body).toEqual({ error: 'Authentication required' }); }); it('should return 403 when non-admin tries to view other user keys', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'bob', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).get('/users/alice/ssh-key-fingerprints'); + currentUser = { username: 'bob', admin: false }; + const res = await request(app).get('/api/v1/user/alice/ssh-key-fingerprints'); expect(res.status).toBe(403); expect(res.body).toEqual({ error: 'Not authorized to view keys for this user' }); }); it('should allow user to view their own keys', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).get('/users/alice/ssh-key-fingerprints'); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).get('/api/v1/user/alice/ssh-key-fingerprints'); expect(res.status).toBe(200); expect(res.body).toEqual([ @@ -155,15 +152,8 @@ describe('Users API', () => { }); it('should allow admin to view any user keys', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'admin', admin: true }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).get('/users/alice/ssh-key-fingerprints'); + currentUser = { username: 'admin', admin: true }; + const res = await request(app).get('/api/v1/user/alice/ssh-key-fingerprints'); expect(res.status).toBe(200); expect(db.getPublicKeys).toHaveBeenCalledWith('alice'); @@ -172,22 +162,15 @@ describe('Users API', () => { it('should handle errors when retrieving keys', async () => { vi.spyOn(db, 'getPublicKeys').mockRejectedValue(new Error('Database error')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).get('/users/alice/ssh-key-fingerprints'); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).get('/api/v1/user/alice/ssh-key-fingerprints'); expect(res.status).toBe(500); expect(res.body).toEqual({ error: 'Failed to retrieve SSH keys' }); }); }); - describe('POST /users/:username/ssh-keys', () => { + describe('POST /api/v1/user/:username/ssh-keys', () => { const validPublicKey = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest test@example.com'; beforeEach(() => { @@ -203,8 +186,9 @@ describe('Users API', () => { }); it('should return 401 when not authenticated', async () => { + currentUser = undefined; const res = await request(app) - .post('/users/alice/ssh-keys') + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(401); @@ -212,16 +196,9 @@ describe('Users API', () => { }); it('should return 403 when non-admin tries to add key for other user', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'bob', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'bob', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(403); @@ -229,15 +206,8 @@ describe('Users API', () => { }); it('should return 400 when public key is missing', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).post('/users/alice/ssh-keys').send({}); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).post('/api/v1/user/alice/ssh-keys').send({}); expect(res.status).toBe(400); expect(res.body).toEqual({ error: 'Public key is required' }); @@ -246,16 +216,9 @@ describe('Users API', () => { it('should return 400 when public key format is invalid', async () => { vi.spyOn(utils, 'parseKey').mockReturnValue(null as any); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: 'invalid-key' }); expect(res.status).toBe(400); @@ -263,16 +226,9 @@ describe('Users API', () => { }); it('should successfully add SSH key', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey, name: 'My Key' }); expect(res.status).toBe(201); @@ -290,16 +246,9 @@ describe('Users API', () => { }); it('should use default name when name not provided', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(201); @@ -314,16 +263,9 @@ describe('Users API', () => { it('should return 409 when key already exists', async () => { vi.spyOn(db, 'addPublicKey').mockRejectedValue(new Error('SSH key already exists')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(409); @@ -333,16 +275,9 @@ describe('Users API', () => { it('should return 404 when user not found', async () => { vi.spyOn(db, 'addPublicKey').mockRejectedValue(new Error('User not found')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(404); @@ -352,16 +287,9 @@ describe('Users API', () => { it('should return 500 for other errors', async () => { vi.spyOn(db, 'addPublicKey').mockRejectedValue(new Error('Database error')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'alice', admin: false }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(500); @@ -369,16 +297,9 @@ describe('Users API', () => { }); it('should allow admin to add key for any user', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'admin', admin: true }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp) - .post('/users/alice/ssh-keys') + currentUser = { username: 'admin', admin: true }; + const res = await request(app) + .post('/api/v1/user/alice/ssh-keys') .send({ publicKey: validPublicKey }); expect(res.status).toBe(201); @@ -386,39 +307,26 @@ describe('Users API', () => { }); }); - describe('DELETE /users/:username/ssh-keys/:fingerprint', () => { + describe('DELETE /api/v1/user/:username/ssh-keys/:fingerprint', () => { it('should return 401 when not authenticated', async () => { - const res = await request(app).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = undefined; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(401); expect(res.body).toEqual({ error: 'Authentication required' }); }); it('should return 403 when non-admin tries to remove key for other user', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'bob', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = { username: 'bob', admin: false }; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(403); expect(res.body).toEqual({ error: 'Not authorized to remove keys for this user' }); }); it('should successfully remove SSH key', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(200); expect(res.body).toEqual({ message: 'SSH key removed successfully' }); @@ -428,15 +336,8 @@ describe('Users API', () => { it('should return 404 when user not found', async () => { vi.spyOn(db, 'removePublicKey').mockRejectedValue(new Error('User not found')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(404); expect(res.body).toEqual({ error: 'User not found' }); @@ -445,30 +346,16 @@ describe('Users API', () => { it('should return 500 for other errors', async () => { vi.spyOn(db, 'removePublicKey').mockRejectedValue(new Error('Database error')); - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'alice', admin: false }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = { username: 'alice', admin: false }; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(500); expect(res.body).toEqual({ error: 'Database error' }); }); it('should allow admin to remove key for any user', async () => { - const testApp = express(); - testApp.use(express.json()); - testApp.use((req, res, next) => { - req.user = { username: 'admin', admin: true }; - next(); - }); - testApp.use('/users', usersRouter); - - const res = await request(testApp).delete('/users/alice/ssh-keys/SHA256:test123'); + currentUser = { username: 'admin', admin: true }; + const res = await request(app).delete('/api/v1/user/alice/ssh-keys/SHA256:test123'); expect(res.status).toBe(200); expect(db.removePublicKey).toHaveBeenCalledWith('alice', 'SHA256:test123'); diff --git a/website/docs/api/auth.mdx b/website/docs/api/auth.mdx index baf238f2c..a3ffce842 100644 --- a/website/docs/api/auth.mdx +++ b/website/docs/api/auth.mdx @@ -5,7 +5,7 @@ description: Auth API endpoints for GitProxy # Auth -*9 endpoints — 5 GET, 4 POST* +*10 endpoints — 6 GET, 4 POST* ## GET `/api/auth` @@ -27,6 +27,17 @@ Returns the enabled authentication methods available to the UI. --- +## GET `/api/auth/csrf-token` + +Returns a CSRF token for the current session, to be sent with subsequent +state-changing requests. + +#### Responses + +- 200 Ok + +--- + ## GET `/api/auth/openidconnect` Initiates the OpenID Connect authentication flow (redirects to the OIDC provider). @@ -76,6 +87,7 @@ Creates a new user. Requires admin privileges. #### Responses - 200 Ok +- 400 Bad Request - 403 Forbidden - 500 Internal Server Error diff --git a/website/docs/api/config.mdx b/website/docs/api/config.mdx index b7df40d10..5dfbc4830 100644 --- a/website/docs/api/config.mdx +++ b/website/docs/api/config.mdx @@ -5,7 +5,7 @@ description: Config API endpoints for GitProxy # Config -*4 endpoints — 4 GET* +*5 endpoints — 5 GET* ## GET `/api/v1/config/attestation` @@ -23,6 +23,14 @@ description: Config API endpoints for GitProxy --- +## GET `/api/v1/config/ssh` + +#### Responses + +- 200 Ok + +--- + ## GET `/api/v1/config/uiRouteAuth` #### Responses diff --git a/website/docs/api/index.mdx b/website/docs/api/index.mdx index ec03d2784..4c70037b0 100644 --- a/website/docs/api/index.mdx +++ b/website/docs/api/index.mdx @@ -8,7 +8,7 @@ description: REST API reference documentation for GitProxy Deploy custom push protections and policies on top of Git. :::info[API Version] -**2.0.0-rc.6** — OpenAPI 3.0 +**2.0.0** — OpenAPI 3.0 ::: ## Authentication @@ -21,12 +21,12 @@ Authorization: Bearer ## Endpoints -30 endpoints across 7 groups: +35 endpoints across 7 groups: -- [**Auth**](/docs/api/auth) — 5 GET, 4 POST -- [**Config**](/docs/api/config) — 4 GET +- [**Auth**](/docs/api/auth) — 6 GET, 4 POST +- [**Config**](/docs/api/config) — 5 GET - [**Health**](/docs/api/health) — 1 GET - [**Home**](/docs/api/home) — 1 GET - [**Push**](/docs/api/push) — 2 GET, 3 POST - [**Repositories**](/docs/api/repositories) — 2 GET, 1 POST, 2 PATCH, 3 DELETE -- [**Users**](/docs/api/users) — 2 GET +- [**Users**](/docs/api/users) — 3 GET, 1 POST, 1 DELETE diff --git a/website/docs/api/repositories.mdx b/website/docs/api/repositories.mdx index 64a90be96..b9e9515e6 100644 --- a/website/docs/api/repositories.mdx +++ b/website/docs/api/repositories.mdx @@ -57,6 +57,8 @@ This endpoint requires a valid **JWT Bearer token** in the `Authorization` heade | `url` | `string` | **Yes** | | | `name` | `string` | **Yes** | | | `project` | `string` | **Yes** | | +| `users` | `object` | No | | +| `_id` | `string` | No | | #### Responses diff --git a/website/docs/api/users.mdx b/website/docs/api/users.mdx index e1c25ceaf..c35947709 100644 --- a/website/docs/api/users.mdx +++ b/website/docs/api/users.mdx @@ -5,7 +5,7 @@ description: Users API endpoints for GitProxy # Users -*2 endpoints — 2 GET* +*5 endpoints — 3 GET, 1 POST, 1 DELETE* ## GET `/api/v1/user`AUTH @@ -41,3 +41,87 @@ This endpoint requires a valid **JWT Bearer token** in the `Authorization` heade - 404 Not Found --- + +## GET `/api/v1/user/{username}/ssh-key-fingerprints`AUTH + +Returns the SSH key fingerprints for a user. +Users may view their own keys; admins may view any user's keys. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `username` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 401 Unauthorized +- 403 Forbidden +- 500 Internal Server Error + +--- + +## POST `/api/v1/user/{username}/ssh-keys`AUTH + +Adds an SSH public key to a user's account. +Users may add keys to their own account; admins may add to any account. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `username` | `path` | `string` | **Yes** | | + +#### Request Body + +| Field | Type | Required | Description | +|-------|------|:--------:|-------------| +| `publicKey` | `string` | No | | +| `name` | `string` | No | | + +#### Responses + +- 200 Ok +- 400 Bad Request +- 401 Unauthorized +- 403 Forbidden +- 404 Not Found +- 409 Conflict +- 500 Internal Server Error + +--- + +## DELETE `/api/v1/user/{username}/ssh-keys/{fingerprint}`AUTH + +Removes an SSH public key from a user's account by fingerprint. +Users may remove keys from their own account; admins may remove from any account. + +:::info[Authorization Required] +This endpoint requires a valid **JWT Bearer token** in the `Authorization` header. +::: + +#### Parameters + +| Name | In | Type | Required | Description | +|------|:---:|------|:--------:|-------------| +| `username` | `path` | `string` | **Yes** | | +| `fingerprint` | `path` | `string` | **Yes** | | + +#### Responses + +- 200 Ok +- 401 Unauthorized +- 403 Forbidden +- 404 Not Found +- 500 Internal Server Error + +---