From 534beeb1514c82da8ce8d2b635ece7fb64d8d390 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Mar 2025 15:32:13 +0900 Subject: [PATCH 01/19] feat(auth): add JWT strategy and fix --- src/service/passport/jwt.js | 26 ++++++++++++++++++++++++++ src/service/passport/oidc.js | 5 ++--- 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/service/passport/jwt.js diff --git a/src/service/passport/jwt.js b/src/service/passport/jwt.js new file mode 100644 index 000000000..f2b2dadb1 --- /dev/null +++ b/src/service/passport/jwt.js @@ -0,0 +1,26 @@ +const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); + +const type = "jwt"; + +const configure = (passport) => { + const JWT_SECRET = process.env.JWT_SECRET; + + if (!JWT_SECRET) { + console.log('JWT secret not provided. Skipping JWT strategy registration.'); + return; // Skip JWT registration if not configured + } + + const opts = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: JWT_SECRET, + }; + + passport.use('jwt', new JwtStrategy(opts, (jwtPayload, done) => { + if (!jwtPayload) return done(null, false); + return done(null, jwtPayload); + })); + + console.log('JWT strategy registered successfully.'); +}; + +module.exports = { configure, type }; diff --git a/src/service/passport/oidc.js b/src/service/passport/oidc.js index baf65a5a7..6681a6a8b 100644 --- a/src/service/passport/oidc.js +++ b/src/service/passport/oidc.js @@ -1,11 +1,10 @@ const openIdClient = require('openid-client'); const { Strategy } = require('openid-client/passport'); -const passport = require('passport'); const db = require('../../db'); let type; -const configure = async () => { +const configure = async (passport) => { const authMethods = require('../../config').getAuthMethods(); const oidcConfig = authMethods.find((method) => method.type.toLowerCase() === "openidconnect")?.oidcConfig; const { issuer, clientID, clientSecret, callbackURL, scope } = oidcConfig; @@ -26,7 +25,7 @@ const configure = async () => { const userInfo = await openIdClient.fetchUserInfo(config, tokenSet.access_token, expectedSub); handleUserAuthentication(userInfo, done); }); - + // currentUrl must be overridden to match the callback URL strategy.currentUrl = (request) => { const callbackUrl = new URL(callbackURL); From 8b1a17363a81ccdc40d925e86e3630dcc616c8f3 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Mar 2025 15:33:23 +0900 Subject: [PATCH 02/19] feat(auth): add dynamicAuthHandler middleware --- package-lock.json | 275 +++++++++++++++++++-- package.json | 4 +- src/service/passport/dynamicAuthHandler.js | 36 +++ src/service/passport/index.js | 3 + src/service/routes/repo.js | 3 +- 5 files changed, 303 insertions(+), 18 deletions(-) create mode 100644 src/service/passport/dynamicAuthHandler.js diff --git a/package-lock.json b/package-lock.json index 65bf8342e..74cc6994e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,10 +38,12 @@ "moment": "^2.29.4", "mongodb": "^5.0.0", "nodemailer": "^6.6.1", - "openid-client": "^6.2.0", + "open": "^10.1.0", + "openid-client": "^6.3.1", "parse-diff": "^0.11.1", "passport": "^0.7.0", "passport-activedirectory": "^1.0.4", + "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "perfect-scrollbar": "^1.5.5", "prop-types": "15.8.1", @@ -4393,6 +4395,27 @@ "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", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5358,6 +5381,34 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -5398,6 +5449,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -5598,6 +5661,15 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7879,6 +7951,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7943,6 +8030,24 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -8224,6 +8329,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -8501,9 +8621,9 @@ } }, "node_modules/jose": { - "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -8650,6 +8770,40 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -8800,6 +8954,27 @@ "dev": true, "license": "MIT" }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9026,11 +9201,40 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", @@ -9055,8 +9259,7 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -9977,9 +10180,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.2.0.tgz", - "integrity": "sha512-2sYwQXuuzGKOHpnM7QL9BssDrly5gKCgJKTyrhmFIHzJRj0fFsr6GVJEdesmrX6NpMg2u63V4hJwRsZE6PUSSA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.3.0.tgz", + "integrity": "sha512-ZlozhPlFfobzh3hB72gnBFLjXpugl/dljz1fJSRdqaV2r3D5dmi5lg2QWI0LmUYuazmE+b5exsloEv6toUtw9g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -10131,14 +10334,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openid-client": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.2.0.tgz", - "integrity": "sha512-pvLVkLcRWNU7YuKKTto376rgL//+rn3ca0XRqsrQVN30lVlpXBPHhSLcGoM/hPbux5p+Ha4tdoz96eEYpyguOQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.3.1.tgz", + "integrity": "sha512-l+uRCCM+KaGKQmCWjrjlFHXa1husuc72OPCCyGB7VjHeEMVldfwsn4Pfb/5Xk51FRqbRakMbwfIUPiAMQDHaSw==", "license": "MIT", "dependencies": { - "jose": "^5.9.6", - "oauth4webapi": "^3.2.0" + "jose": "^5.10.0", + "oauth4webapi": "^3.3.0" }, "funding": { "url": "https://github.com/sponsors/panva" @@ -10342,6 +10563,16 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, "node_modules/passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -11163,6 +11394,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index cb4ffa98d..40956086b 100644 --- a/package.json +++ b/package.json @@ -59,10 +59,12 @@ "moment": "^2.29.4", "mongodb": "^5.0.0", "nodemailer": "^6.6.1", - "openid-client": "^6.2.0", + "open": "^10.1.0", + "openid-client": "^6.3.1", "parse-diff": "^0.11.1", "passport": "^0.7.0", "passport-activedirectory": "^1.0.4", + "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "perfect-scrollbar": "^1.5.5", "prop-types": "15.8.1", diff --git a/src/service/passport/dynamicAuthHandler.js b/src/service/passport/dynamicAuthHandler.js new file mode 100644 index 000000000..98a557887 --- /dev/null +++ b/src/service/passport/dynamicAuthHandler.js @@ -0,0 +1,36 @@ +const passport = require('./index').getPassport(); + +/** + * Dynamic authentication middleware that supports JWT and session auth. + * If JWT strategy is set up, it will be prioritized. Otherwise, it will fallback to session auth. + * If either strategy is successful, it calls the next middleware in the chain. + * @param {*} passport the passport instance + * @returns a middleware function that handles authentication dynamically + */ +const dynamicAuthHandler = () => { + return (req, res, next) => { + console.log(`Dynamic Auth triggered - Requested URL: ${req.originalUrl}`); + const hasJwtStrategy = !!passport._strategy('jwt'); + console.log('hasJwtStrategy: ' + hasJwtStrategy); + if (hasJwtStrategy) { + // Try JWT authentication first + passport.authenticate('jwt', { session: false }, (err, user, info) => { + console.log(`JWT Auth triggered - User: ${user} - Error: ${err}`); + if (err) return next(err); + if (user) { + req.user = user; // JWT authenticated user + return next(); + } + // Fallback to session if available + if (req.isAuthenticated && req.isAuthenticated()) return next(); + return res.status(401).json({ message: 'Unauthorized: Valid token or session required.' }); + })(req, res, next); + } else { + // Default to session auth + if (req.isAuthenticated && req.isAuthenticated()) return next(); + return res.status(401).json({ message: 'Unauthorized: No active session.' }); + } + }; +} + +module.exports = dynamicAuthHandler; diff --git a/src/service/passport/index.js b/src/service/passport/index.js index ce62719d2..7126ae10f 100644 --- a/src/service/passport/index.js +++ b/src/service/passport/index.js @@ -2,6 +2,7 @@ const passport = require("passport"); const local = require('./local'); const activeDirectory = require('./activeDirectory'); const oidc = require('./oidc'); +const jwt = require('./jwt'); const config = require('../../config'); // Allows obtaining strategy config function and type @@ -10,12 +11,14 @@ const authStrategies = { local: local, activedirectory: activeDirectory, openidconnect: oidc, + jwt: jwt, }; const configure = async () => { passport.initialize(); const authMethods = config.getAuthMethods(); + console.log(`authMethods: ${JSON.stringify(authMethods)}`); for (const auth of authMethods) { const strategy = authStrategies[auth.type.toLowerCase()]; diff --git a/src/service/routes/repo.js b/src/service/routes/repo.js index 1f6698e3b..36a7b88f2 100644 --- a/src/service/routes/repo.js +++ b/src/service/routes/repo.js @@ -2,8 +2,9 @@ const express = require('express'); const router = new express.Router(); const db = require('../../db'); const { getProxyURL } = require('../urls'); +const dynamicAuthHandler = require('../passport/dynamicAuthHandler'); -router.get('/', async (req, res) => { +router.get('/', dynamicAuthHandler(), async (req, res) => { const proxyURL = getProxyURL(req); const query = { type: 'push', From 6265bbd60de69bd3ab527a334b42ea351a8147cc Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 13:28:56 +0900 Subject: [PATCH 03/19] feat(auth): update JWT auth middleware --- src/service/passport/dynamicAuthHandler.js | 36 ------- src/service/passport/jwtAuthHandler.js | 106 +++++++++++++++++++++ 2 files changed, 106 insertions(+), 36 deletions(-) delete mode 100644 src/service/passport/dynamicAuthHandler.js create mode 100644 src/service/passport/jwtAuthHandler.js diff --git a/src/service/passport/dynamicAuthHandler.js b/src/service/passport/dynamicAuthHandler.js deleted file mode 100644 index 98a557887..000000000 --- a/src/service/passport/dynamicAuthHandler.js +++ /dev/null @@ -1,36 +0,0 @@ -const passport = require('./index').getPassport(); - -/** - * Dynamic authentication middleware that supports JWT and session auth. - * If JWT strategy is set up, it will be prioritized. Otherwise, it will fallback to session auth. - * If either strategy is successful, it calls the next middleware in the chain. - * @param {*} passport the passport instance - * @returns a middleware function that handles authentication dynamically - */ -const dynamicAuthHandler = () => { - return (req, res, next) => { - console.log(`Dynamic Auth triggered - Requested URL: ${req.originalUrl}`); - const hasJwtStrategy = !!passport._strategy('jwt'); - console.log('hasJwtStrategy: ' + hasJwtStrategy); - if (hasJwtStrategy) { - // Try JWT authentication first - passport.authenticate('jwt', { session: false }, (err, user, info) => { - console.log(`JWT Auth triggered - User: ${user} - Error: ${err}`); - if (err) return next(err); - if (user) { - req.user = user; // JWT authenticated user - return next(); - } - // Fallback to session if available - if (req.isAuthenticated && req.isAuthenticated()) return next(); - return res.status(401).json({ message: 'Unauthorized: Valid token or session required.' }); - })(req, res, next); - } else { - // Default to session auth - if (req.isAuthenticated && req.isAuthenticated()) return next(); - return res.status(401).json({ message: 'Unauthorized: No active session.' }); - } - }; -} - -module.exports = dynamicAuthHandler; diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js new file mode 100644 index 000000000..a112d8cb9 --- /dev/null +++ b/src/service/passport/jwtAuthHandler.js @@ -0,0 +1,106 @@ +const axios = require("axios"); +const jwt = require("jsonwebtoken"); +const jwkToPem = require("jwk-to-pem"); + +/** + * Obtain the JSON Web Key Set (JWKS) from the OIDC authority. + * @param {string} authorityUrl the OIDC authority URL. e.g. https://login.microsoftonline.com/{tenantId} + * @returns {Promise} the JWKS keys + */ +async function getJwks(authorityUrl) { + try { + const { data } = await axios.get(`${authorityUrl}/.well-known/openid-configuration`); + const jwksUri = data.jwks_uri; + + const { data: jwks } = await axios.get(jwksUri); + return jwks.keys; + } catch (error) { + console.error("Error fetching JWKS:", error); + throw new Error("Failed to fetch JWKS"); + } +} + +/** + * Validate a JWT token using the OIDC configuration. + * @param {*} token the JWT token + * @param {*} authorityUrl the OIDC authority URL + * @param {*} clientID the OIDC client ID + * @param {*} expectedAudience the expected audience for the token + * @returns {Promise} the verified payload or an error + */ +async function validateJwt(token, authorityUrl, clientID, expectedAudience) { + try { + const jwks = await getJwks(authorityUrl); + + const decodedHeader = await jwt.decode(token, { complete: true }); + if (!decodedHeader || !decodedHeader.header || !decodedHeader.header.kid) { + throw new Error("Invalid JWT: Missing key ID (kid)"); + } + + const { kid } = decodedHeader.header; + const jwk = jwks.find((key) => key.kid === kid); + if (!jwk) { + throw new Error("No matching key found in JWKS"); + } + + const pubKey = jwkToPem(jwk); + + const verifiedPayload = jwt.verify(token, pubKey, { + algorithms: ["RS256"], + issuer: authorityUrl, + audience: expectedAudience, + }); + + if (verifiedPayload.azp !== clientID) { + throw new Error("JWT client ID does not match"); + } + + return { verifiedPayload }; + } catch (error) { + const errorMessage = `JWT validation failed: ${error.message}`; + console.error(errorMessage); + return { error: errorMessage }; + } +} + +const jwtAuthHandler = () => { + return async (req, res, next) => { + if (process.env.OIDC_AUTH_ENABLED !== "true") { + return next(); + } + + if (req.isAuthenticated()) { + console.log('request is already authenticated'); + return next(); + } + + const token = req.header("Authorization"); + if (!token) { + return res.status(401).send("No token provided"); + } + + const clientID = process.env.OIDC_CLIENT_ID; + const authorityUrl = process.env.OIDC_AUTHORITY; + const expectedAudience = process.env.OIDC_AUDIENCE || clientID; + + if (!authorityUrl) { + return res.status(500).send("OIDC authority URL is not configured"); + } + + if (!clientID) { + return res.status(500).send("OIDC client ID is not configured"); + } + + const tokenParts = token.split(" "); + const { verifiedPayload, error } = await validateJwt(tokenParts[1], authorityUrl, expectedAudience, clientID); + if (error) { + return res.status(401).send(error); + } + + req.user = verifiedPayload; + console.log('request authenticated through JWT') + return next(); + } +} + +module.exports = jwtAuthHandler; From 5202f7eecc86c441a8ca81952a2b672c022be88c Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 13:42:49 +0900 Subject: [PATCH 04/19] feat(auth): add jwk-to-pem library for jwt validation --- package-lock.json | 60 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 61 insertions(+) diff --git a/package-lock.json b/package-lock.json index 74cc6994e..005f0d811 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "history": "5.3.0", "isomorphic-git": "^1.27.1", "jsonschema": "^1.4.1", + "jwk-to-pem": "^2.0.7", "load-plugin": "^6.0.0", "lodash": "^4.17.21", "lusca": "^1.7.0", @@ -4315,6 +4316,12 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -5682,6 +5689,21 @@ "dev": true, "license": "ISC" }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -7439,6 +7461,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -7511,6 +7543,17 @@ "@babel/runtime": "^7.7.6" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hogan.js": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", @@ -8965,6 +9008,17 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwk-to-pem": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.7.tgz", + "integrity": "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ==", + "license": "Apache-2.0", + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.6.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -9561,6 +9615,12 @@ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 40956086b..a6b25aa16 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "history": "5.3.0", "isomorphic-git": "^1.27.1", "jsonschema": "^1.4.1", + "jwk-to-pem": "^2.0.7", "load-plugin": "^6.0.0", "lodash": "^4.17.21", "lusca": "^1.7.0", From e8d08780c3366a46e46f2eb21b73ecfec3261579 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 13:57:18 +0900 Subject: [PATCH 05/19] feat(auth): convert envs to proxy.config.json entries (jwt) --- src/service/passport/jwtAuthHandler.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index a112d8cb9..8208cc464 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -65,7 +65,11 @@ async function validateJwt(token, authorityUrl, clientID, expectedAudience) { const jwtAuthHandler = () => { return async (req, res, next) => { - if (process.env.OIDC_AUTH_ENABLED !== "true") { + const authMethods = require('../../config').getAuthMethods(); + const jwtAuthMethod = authMethods.find((method) => method.type.toLowerCase() === "jwt"); + const { clientID, authorityURL, expectedAudience } = jwtAuthMethod?.jwtConfig; + + if (jwtAuthMethod.enabled) { return next(); } @@ -79,11 +83,9 @@ const jwtAuthHandler = () => { return res.status(401).send("No token provided"); } - const clientID = process.env.OIDC_CLIENT_ID; - const authorityUrl = process.env.OIDC_AUTHORITY; - const expectedAudience = process.env.OIDC_AUDIENCE || clientID; + let audience = expectedAudience || clientID; - if (!authorityUrl) { + if (!authorityURL) { return res.status(500).send("OIDC authority URL is not configured"); } @@ -92,13 +94,12 @@ const jwtAuthHandler = () => { } const tokenParts = token.split(" "); - const { verifiedPayload, error } = await validateJwt(tokenParts[1], authorityUrl, expectedAudience, clientID); + const { verifiedPayload, error } = await validateJwt(tokenParts[1], authorityURL, audience, clientID); if (error) { return res.status(401).send(error); } req.user = verifiedPayload; - console.log('request authenticated through JWT') return next(); } } From c5b45b2b2dfbfb631cd1eb8cffb44a9ca3128f64 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 14:07:19 +0900 Subject: [PATCH 06/19] feat(auth): add missing proxy.config.json auth methods --- proxy.config.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/proxy.config.json b/proxy.config.json index 14d016e4d..4e6863450 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -49,6 +49,29 @@ "baseDN": "", "searchBase": "" } + }, + { + "type": "", + "enabled": false, + "oidcConfig": { + "issuer": "", + "clientID": "", + "clientSecret": "", + "authorizationURL": "", + "tokenURL": "", + "userInfoURL": "", + "callbackURL": "", + "scope": "" + } + }, + { + "type": "jwt", + "enabled": false, + "jwtConfig": { + "clientID": "", + "authorityURL": "", + "expecetedAudience": "" + } } ], "api": { From b0c500c00673d02a0ef9921621489d34e674f364 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 14:36:10 +0900 Subject: [PATCH 07/19] fix(auth): fix undefined errors --- proxy.config.json | 2 +- src/service/passport/jwtAuthHandler.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/proxy.config.json b/proxy.config.json index 4e6863450..dadcf7346 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -51,7 +51,7 @@ } }, { - "type": "", + "type": "oidc", "enabled": false, "oidcConfig": { "issuer": "", diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 8208cc464..af97e8640 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -67,9 +67,7 @@ const jwtAuthHandler = () => { return async (req, res, next) => { const authMethods = require('../../config').getAuthMethods(); const jwtAuthMethod = authMethods.find((method) => method.type.toLowerCase() === "jwt"); - const { clientID, authorityURL, expectedAudience } = jwtAuthMethod?.jwtConfig; - - if (jwtAuthMethod.enabled) { + if (!jwtAuthMethod) { return next(); } @@ -83,6 +81,7 @@ const jwtAuthHandler = () => { return res.status(401).send("No token provided"); } + const { clientID, authorityURL, expectedAudience } = jwtAuthMethod?.jwtConfig; let audience = expectedAudience || clientID; if (!authorityURL) { From 1446fcddbc979f0815580cee1db4fa694aceef52 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 14:37:42 +0900 Subject: [PATCH 08/19] fix(auth): fix mislabeled auth method --- proxy.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy.config.json b/proxy.config.json index dadcf7346..07d3b1af6 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -51,7 +51,7 @@ } }, { - "type": "oidc", + "type": "openidconnect", "enabled": false, "oidcConfig": { "issuer": "", From 85b3fede87bade91a7184406b5f4c39c24ddc821 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 14:46:52 +0900 Subject: [PATCH 09/19] feat(auth): set jwtAuthHandler middleware for /push, /repo and /user routes --- src/service/routes/index.js | 7 ++++--- src/service/routes/repo.js | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/service/routes/index.js b/src/service/routes/index.js index a7529a4ac..45b276c17 100644 --- a/src/service/routes/index.js +++ b/src/service/routes/index.js @@ -6,14 +6,15 @@ const repo = require('./repo'); const users = require('./users'); const healthcheck = require('./healthcheck'); const config = require('./config'); +const jwtAuthHandler = require('../passport/jwtAuthHandler'); const router = new express.Router(); router.use('/api', home); router.use('/api/auth', auth); router.use('/api/v1/healthcheck', healthcheck); -router.use('/api/v1/push', push); -router.use('/api/v1/repo', repo); -router.use('/api/v1/user', users); +router.use('/api/v1/push', jwtAuthHandler(), push); +router.use('/api/v1/repo', jwtAuthHandler(), repo); +router.use('/api/v1/user', jwtAuthHandler(), users); router.use('/api/v1/config', config); module.exports = router; diff --git a/src/service/routes/repo.js b/src/service/routes/repo.js index 36a7b88f2..1f6698e3b 100644 --- a/src/service/routes/repo.js +++ b/src/service/routes/repo.js @@ -2,9 +2,8 @@ const express = require('express'); const router = new express.Router(); const db = require('../../db'); const { getProxyURL } = require('../urls'); -const dynamicAuthHandler = require('../passport/dynamicAuthHandler'); -router.get('/', dynamicAuthHandler(), async (req, res) => { +router.get('/', async (req, res) => { const proxyURL = getProxyURL(req); const query = { type: 'push', From 3410c2940f1681cf0b7de2ac1b229b0a41a2482b Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 15:12:10 +0900 Subject: [PATCH 10/19] chore(auth): fix linter errors --- src/config/index.js | 2 +- src/service/passport/jwtAuthHandler.js | 4 ++-- src/service/passport/oidc.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/index.js b/src/config/index.js index ddc4545f5..f9754859a 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -72,7 +72,7 @@ const getDatabase = () => { /** * Get the list of enabled authentication methods - * @returns {Array} List of enabled authentication methods + * @return {Array} List of enabled authentication methods */ const getAuthMethods = () => { if (_userSettings !== null && _userSettings.authentication) { diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index af97e8640..336c16e90 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -5,7 +5,7 @@ const jwkToPem = require("jwk-to-pem"); /** * Obtain the JSON Web Key Set (JWKS) from the OIDC authority. * @param {string} authorityUrl the OIDC authority URL. e.g. https://login.microsoftonline.com/{tenantId} - * @returns {Promise} the JWKS keys + * @return {Promise} the JWKS keys */ async function getJwks(authorityUrl) { try { @@ -26,7 +26,7 @@ async function getJwks(authorityUrl) { * @param {*} authorityUrl the OIDC authority URL * @param {*} clientID the OIDC client ID * @param {*} expectedAudience the expected audience for the token - * @returns {Promise} the verified payload or an error + * @return {Promise} the verified payload or an error */ async function validateJwt(token, authorityUrl, clientID, expectedAudience) { try { diff --git a/src/service/passport/oidc.js b/src/service/passport/oidc.js index 6681a6a8b..1adbfb215 100644 --- a/src/service/passport/oidc.js +++ b/src/service/passport/oidc.js @@ -63,7 +63,7 @@ const configure = async (passport) => { * Handles user authentication with OIDC. * @param userInfo the OIDC user info object * @param done the callback function - * @returns a promise with the authenticated user or an error + * @return a promise with the authenticated user or an error */ const handleUserAuthentication = async (userInfo, done) => { try { From 2d680adcee6e4754692f17e4397f20d9e6053ca1 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Mar 2025 15:28:25 +0900 Subject: [PATCH 11/19] chore(auth): fix linting errors --- package-lock.json | 134 ------------------------- package.json | 1 - src/service/passport/jwtAuthHandler.js | 4 +- src/service/passport/oidc.js | 10 +- src/ui/views/Login/Login.jsx | 3 +- 5 files changed, 8 insertions(+), 144 deletions(-) diff --git a/package-lock.json b/package-lock.json index 005f0d811..a5563293b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,6 @@ "moment": "^2.29.4", "mongodb": "^5.0.0", "nodemailer": "^6.6.1", - "open": "^10.1.0", "openid-client": "^6.3.1", "parse-diff": "^0.11.1", "passport": "^0.7.0", @@ -4408,21 +4407,6 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5388,34 +5372,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -5456,18 +5412,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -7994,21 +7938,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8073,24 +8002,6 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -8372,21 +8283,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10394,24 +10290,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/openid-client": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.3.1.tgz", @@ -11454,18 +11332,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index a6b25aa16..939a67b9f 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "moment": "^2.29.4", "mongodb": "^5.0.0", "nodemailer": "^6.6.1", - "open": "^10.1.0", "openid-client": "^6.3.1", "parse-diff": "^0.11.1", "passport": "^0.7.0", diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 336c16e90..58db67964 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -81,8 +81,8 @@ const jwtAuthHandler = () => { return res.status(401).send("No token provided"); } - const { clientID, authorityURL, expectedAudience } = jwtAuthMethod?.jwtConfig; - let audience = expectedAudience || clientID; + const { clientID, authorityURL, expectedAudience } = jwtAuthMethod.jwtConfig; + const audience = expectedAudience || clientID; if (!authorityURL) { return res.status(500).send("OIDC authority URL is not configured"); diff --git a/src/service/passport/oidc.js b/src/service/passport/oidc.js index 1adbfb215..057f352eb 100644 --- a/src/service/passport/oidc.js +++ b/src/service/passport/oidc.js @@ -27,7 +27,7 @@ const configure = async (passport) => { }); // currentUrl must be overridden to match the callback URL - strategy.currentUrl = (request) => { + strategy.currentUrl = function(request) { const callbackUrl = new URL(callbackURL); const currentUrl = Strategy.prototype.currentUrl.call(this, request); currentUrl.host = callbackUrl.host; @@ -61,13 +61,13 @@ const configure = async (passport) => { /** * Handles user authentication with OIDC. - * @param userInfo the OIDC user info object - * @param done the callback function - * @return a promise with the authenticated user or an error + * @param {*} userInfo the OIDC user info object + * @param {*} done the callback function + * @return {*} a promise with the authenticated user or an error */ const handleUserAuthentication = async (userInfo, done) => { try { - let user = await db.findUserByOIDC(userInfo.sub); + const user = await db.findUserByOIDC(userInfo.sub); if (!user) { const email = safelyExtractEmail(userInfo); diff --git a/src/ui/views/Login/Login.jsx b/src/ui/views/Login/Login.jsx index 0be3781a6..ec8b3debd 100644 --- a/src/ui/views/Login/Login.jsx +++ b/src/ui/views/Login/Login.jsx @@ -24,8 +24,7 @@ export default function UserProfile() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [message, setMessage] = useState(''); - const [success, setSuccess] = useState(false); - const [gitAccountError, setGitAccountError] = useState(false); + const [, setGitAccountError] = useState(false); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); From e259081507772e842c262989efcc3d0566c11056 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Wed, 2 Apr 2025 23:10:24 +0900 Subject: [PATCH 12/19] fix: add CI debug line and fix config typo --- proxy.config.json | 2 +- test/testLogin.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxy.config.json b/proxy.config.json index 07d3b1af6..7fa4ef059 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -70,7 +70,7 @@ "jwtConfig": { "clientID": "", "authorityURL": "", - "expecetedAudience": "" + "expectedAudience": "" } } ], diff --git a/test/testLogin.test.js b/test/testLogin.test.js index 833184e0b..79e4fb1a7 100644 --- a/test/testLogin.test.js +++ b/test/testLogin.test.js @@ -31,6 +31,13 @@ describe('auth', async () => { password: 'admin', }); + // Debug CI + console.log("DEBUG testLogin.test.js: "); + console.log(`res.body: ${JSON.stringify(res.body)}`); + console.log(`res.headers: ${JSON.stringify(res.headers)}`); + console.log(`res.status: ${res.status}`); + console.log(`res.text: ${res.text}`); + expect(res).to.have.cookie('connect.sid'); res.should.have.status(200); From 04c3878aba77120b60f3f7d6cf7c0f0916914b6d Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 4 Apr 2025 11:45:43 +0900 Subject: [PATCH 13/19] fix: fix createDefaultAdmin bug and add debug lines for CI --- src/service/passport/local.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/service/passport/local.js b/src/service/passport/local.js index 746434c6c..c292de57e 100644 --- a/src/service/passport/local.js +++ b/src/service/passport/local.js @@ -13,6 +13,8 @@ const configure = async (passport) => { return done(null, false, { message: "Incorrect username." }); } + console.log(`Before bcrypt compare: user.password: ${user.password}`); + console.log(`Before bcrypt compare: password: ${password}`); const passwordCorrect = await bcrypt.compare(password, user.password); if (!passwordCorrect) { return done(null, false, { message: "Incorrect password." }); @@ -47,7 +49,8 @@ const configure = async (passport) => { const createDefaultAdmin = async () => { const admin = await db.findUser("admin"); if (!admin) { - await db.createUser("admin", "admin", "admin@place.com", "none", true, true, true, true); + console.log("No admin user found. Creating default admin user..."); + await db.createUser("admin", "admin", "admin@place.com", "none", true); } }; From 3605d73c89cb0ceedd36b4db8dcfc95819a6cd24 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 4 Apr 2025 11:58:39 +0900 Subject: [PATCH 14/19] fix: remove debug lines --- src/service/passport/jwtAuthHandler.js | 1 - src/service/passport/local.js | 3 --- src/service/passport/oidc.js | 2 +- src/ui/auth/AuthProvider.tsx | 3 --- src/ui/components/PrivateRoute/PrivateRoute.tsx | 6 +----- test/testLogin.test.js | 7 ------- 6 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 58db67964..faa039f50 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -72,7 +72,6 @@ const jwtAuthHandler = () => { } if (req.isAuthenticated()) { - console.log('request is already authenticated'); return next(); } diff --git a/src/service/passport/local.js b/src/service/passport/local.js index c292de57e..579d47234 100644 --- a/src/service/passport/local.js +++ b/src/service/passport/local.js @@ -13,8 +13,6 @@ const configure = async (passport) => { return done(null, false, { message: "Incorrect username." }); } - console.log(`Before bcrypt compare: user.password: ${user.password}`); - console.log(`Before bcrypt compare: password: ${password}`); const passwordCorrect = await bcrypt.compare(password, user.password); if (!passwordCorrect) { return done(null, false, { message: "Incorrect password." }); @@ -49,7 +47,6 @@ const configure = async (passport) => { const createDefaultAdmin = async () => { const admin = await db.findUser("admin"); if (!admin) { - console.log("No admin user found. Creating default admin user..."); await db.createUser("admin", "admin", "admin@place.com", "none", true); } }; diff --git a/src/service/passport/oidc.js b/src/service/passport/oidc.js index 284827e5a..6c9cd00a7 100644 --- a/src/service/passport/oidc.js +++ b/src/service/passport/oidc.js @@ -50,7 +50,7 @@ const configure = async (passport) => { done(err); } }) - console.log(`setting type to ${server.host}`) + type = server.host; return passport; diff --git a/src/ui/auth/AuthProvider.tsx b/src/ui/auth/AuthProvider.tsx index 1da89df51..f7ab4b2fc 100644 --- a/src/ui/auth/AuthProvider.tsx +++ b/src/ui/auth/AuthProvider.tsx @@ -16,13 +16,10 @@ export const AuthProvider = ({ children }) => { const [isLoading, setIsLoading] = useState(true); const refreshUser = async () => { - console.log('Refreshing user'); try { const data = await getUserInfo(); setUser(data); - console.log('User refreshed:', data); } catch (error) { - console.error('Error refreshing user:', error); setUser(null); } finally { setIsLoading(false); diff --git a/src/ui/components/PrivateRoute/PrivateRoute.tsx b/src/ui/components/PrivateRoute/PrivateRoute.tsx index 4e7a2f4bf..d010a7d86 100644 --- a/src/ui/components/PrivateRoute/PrivateRoute.tsx +++ b/src/ui/components/PrivateRoute/PrivateRoute.tsx @@ -4,20 +4,16 @@ import { useAuth } from '../../auth/AuthProvider'; const PrivateRoute = ({ component: Component, adminOnly = false }) => { const { user, isLoading } = useAuth(); - console.debug('PrivateRoute', { user, isLoading, adminOnly }); - + if (isLoading) { - console.debug('Auth is loading, waiting'); return
Loading...
; // TODO: Add loading spinner } if (!user) { - console.debug('User not logged in, redirecting to login page'); return ; } if (adminOnly && !user.admin) { - console.debug('User is not an admin, redirecting to not authorized page'); return ; } diff --git a/test/testLogin.test.js b/test/testLogin.test.js index 79e4fb1a7..833184e0b 100644 --- a/test/testLogin.test.js +++ b/test/testLogin.test.js @@ -31,13 +31,6 @@ describe('auth', async () => { password: 'admin', }); - // Debug CI - console.log("DEBUG testLogin.test.js: "); - console.log(`res.body: ${JSON.stringify(res.body)}`); - console.log(`res.headers: ${JSON.stringify(res.headers)}`); - console.log(`res.status: ${res.status}`); - console.log(`res.text: ${res.text}`); - expect(res).to.have.cookie('connect.sid'); res.should.have.status(200); From 1e5d3610ff29c9d71083328c17264c1fec9967ab Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 4 Apr 2025 12:10:23 +0900 Subject: [PATCH 15/19] chore: allow OFL-1.1 license Free license according to FSF (https://www.gnu.org/licenses/license-list.html#SILOFL) --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fdbc7ecb1..dd4c21bc5 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -21,6 +21,6 @@ jobs: with: comment-summary-in-pr: always fail-on-severity: high - allow-licenses: MIT, MIT-0, Apache-2.0, BSD-3-Clause, BSD-3-Clause-Clear, ISC, BSD-2-Clause, Unlicense, CC0-1.0, 0BSD, X11, MPL-2.0, MPL-1.0, MPL-1.1, MPL-2.0, Zlib + allow-licenses: MIT, MIT-0, Apache-2.0, BSD-3-Clause, BSD-3-Clause-Clear, ISC, BSD-2-Clause, Unlicense, CC0-1.0, 0BSD, X11, MPL-2.0, MPL-1.0, MPL-1.1, MPL-2.0, OFL-1.1, Zlib fail-on-scopes: development, runtime allow-dependencies-licenses: 'pkg:npm/caniuse-lite' From 634640ff92c1dbea6763bfef491767a9cb3ca80a Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Tue, 8 Apr 2025 00:18:54 +0900 Subject: [PATCH 16/19] fix: add line breaks to jwt auth error messages --- src/service/passport/jwtAuthHandler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index faa039f50..2989cf65d 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -57,7 +57,7 @@ async function validateJwt(token, authorityUrl, clientID, expectedAudience) { return { verifiedPayload }; } catch (error) { - const errorMessage = `JWT validation failed: ${error.message}`; + const errorMessage = `JWT validation failed: ${error.message}\n`; console.error(errorMessage); return { error: errorMessage }; } @@ -77,18 +77,18 @@ const jwtAuthHandler = () => { const token = req.header("Authorization"); if (!token) { - return res.status(401).send("No token provided"); + return res.status(401).send("No token provided\n"); } const { clientID, authorityURL, expectedAudience } = jwtAuthMethod.jwtConfig; const audience = expectedAudience || clientID; if (!authorityURL) { - return res.status(500).send("OIDC authority URL is not configured"); + return res.status(500).send("OIDC authority URL is not configured\n"); } if (!clientID) { - return res.status(500).send("OIDC client ID is not configured"); + return res.status(500).send("OIDC client ID is not configured\n"); } const tokenParts = token.split(" "); From edf4f7daef2bf0236b019339eff72c9a5e5da8c2 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 10 Apr 2025 16:47:56 +0900 Subject: [PATCH 17/19] refactor(auth): convert jwt auth to API-only (remove jwt passport strategy) --- config.schema.json | 7 +++++ package-lock.json | 12 +------- package.json | 2 +- proxy.config.json | 41 +++++++++++++------------- src/config/index.js | 20 +++++++++++++ src/service/passport/index.js | 2 -- src/service/passport/jwt.js | 26 ---------------- src/service/passport/jwtAuthHandler.js | 4 +-- 8 files changed, 52 insertions(+), 62 deletions(-) delete mode 100644 src/service/passport/jwt.js diff --git a/config.schema.json b/config.schema.json index 771e83d0c..135018be9 100644 --- a/config.schema.json +++ b/config.schema.json @@ -78,6 +78,13 @@ "type": "object" } } + }, + "apiAuthentication": { + "description": "List of authentication sources for API endpoints. May be empty, in which case all endpoints are public.", + "type": "array", + "items": { + "$ref": "#/definitions/authentication" + } } }, "definitions": { diff --git a/package-lock.json b/package-lock.json index f580e9ff5..1a84fabc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "history": "5.3.0", "isomorphic-git": "^1.27.1", "jsonschema": "^1.4.1", + "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.7", "load-plugin": "^6.0.0", "lodash": "^4.17.21", @@ -43,7 +44,6 @@ "parse-diff": "^0.11.1", "passport": "^0.7.0", "passport-activedirectory": "^1.0.4", - "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "perfect-scrollbar": "^1.5.5", "prop-types": "15.8.1", @@ -10504,16 +10504,6 @@ "url": "https://github.com/sponsors/jaredhanson" } }, - "node_modules/passport-jwt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", - "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", - "license": "MIT", - "dependencies": { - "jsonwebtoken": "^9.0.0", - "passport-strategy": "^1.0.0" - } - }, "node_modules/passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", diff --git a/package.json b/package.json index b0896fcd4..d820d2c13 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "history": "5.3.0", "isomorphic-git": "^1.27.1", "jsonschema": "^1.4.1", + "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.7", "load-plugin": "^6.0.0", "lodash": "^4.17.21", @@ -64,7 +65,6 @@ "parse-diff": "^0.11.1", "passport": "^0.7.0", "passport-activedirectory": "^1.0.4", - "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "perfect-scrollbar": "^1.5.5", "prop-types": "15.8.1", diff --git a/proxy.config.json b/proxy.config.json index 7fa4ef059..d494898e8 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -49,28 +49,19 @@ "baseDN": "", "searchBase": "" } - }, + }, { "type": "openidconnect", - "enabled": false, + "enabled": true, "oidcConfig": { - "issuer": "", - "clientID": "", - "clientSecret": "", - "authorizationURL": "", - "tokenURL": "", - "userInfoURL": "", - "callbackURL": "", - "scope": "" - } - }, - { - "type": "jwt", - "enabled": false, - "jwtConfig": { - "clientID": "", - "authorityURL": "", - "expectedAudience": "" + "issuer": "https://accounts.google.com", + "clientID": "1009968223893-u92qq6itk7ej5008o4174gjubs5lhorg.apps.googleusercontent.com", + "clientSecret": "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", + "authorizationURL": "https://accounts.google.com/o/oauth2/auth", + "tokenURL": "https://oauth2.googleapis.com/token", + "userInfoURL": "https://openidconnect.googleapis.com/v1/userinfo", + "callbackURL": "http://localhost:8080/api/auth/oidc/callback", + "scope": "openid email profile" } } ], @@ -120,5 +111,15 @@ "urlShortener": "", "contactEmail": "", "csrfProtection": true, - "plugins": [] + "plugins": [], + "apiAuthentication": [ + { + "type": "jwt", + "enabled": true, + "jwtConfig": { + "clientID": "1009968223893-u92qq6itk7ej5008o4174gjubs5lhorg.apps.googleusercontent.com", + "authorityURL": "https://accounts.google.com" + } + } + ] } diff --git a/src/config/index.js b/src/config/index.js index f9754859a..c00ca1de0 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -10,6 +10,7 @@ if (fs.existsSync(userSettingsPath)) { let _authorisedList = defaultSettings.authorisedList; let _database = defaultSettings.sink; let _authentication = defaultSettings.authentication; +let _apiAuthentication = defaultSettings.apiAuthentication; let _tempPassword = defaultSettings.tempPassword; let _proxyUrl = defaultSettings.proxyUrl; let _api = defaultSettings.api; @@ -88,6 +89,24 @@ const getAuthMethods = () => { return enabledAuthMethods; }; +/** + * Get the list of enabled authentication methods for API endpoints + * @return {Array} List of enabled authentication methods + */ +const getAPIAuthMethods = () => { + if (_userSettings !== null && _userSettings.apiAuthentication) { + _apiAuthentication = _userSettings.apiAuthentication; + } + + const enabledAuthMethods = _apiAuthentication.filter(auth => auth.enabled); + + if (enabledAuthMethods.length === 0) { + throw new Error("No authentication method enabled."); + } + + return enabledAuthMethods; +}; + // Log configuration to console const logConfiguration = () => { console.log(`authorisedList = ${JSON.stringify(getAuthorisedList())}`); @@ -205,6 +224,7 @@ exports.getAuthorisedList = getAuthorisedList; exports.getDatabase = getDatabase; exports.logConfiguration = logConfiguration; exports.getAuthMethods = getAuthMethods; +exports.getAPIAuthMethods = getAPIAuthMethods; exports.getTempPasswordConfig = getTempPasswordConfig; exports.getCookieSecret = getCookieSecret; exports.getSessionMaxAgeHours = getSessionMaxAgeHours; diff --git a/src/service/passport/index.js b/src/service/passport/index.js index 7126ae10f..533577d13 100644 --- a/src/service/passport/index.js +++ b/src/service/passport/index.js @@ -2,7 +2,6 @@ const passport = require("passport"); const local = require('./local'); const activeDirectory = require('./activeDirectory'); const oidc = require('./oidc'); -const jwt = require('./jwt'); const config = require('../../config'); // Allows obtaining strategy config function and type @@ -11,7 +10,6 @@ const authStrategies = { local: local, activedirectory: activeDirectory, openidconnect: oidc, - jwt: jwt, }; const configure = async () => { diff --git a/src/service/passport/jwt.js b/src/service/passport/jwt.js deleted file mode 100644 index f2b2dadb1..000000000 --- a/src/service/passport/jwt.js +++ /dev/null @@ -1,26 +0,0 @@ -const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); - -const type = "jwt"; - -const configure = (passport) => { - const JWT_SECRET = process.env.JWT_SECRET; - - if (!JWT_SECRET) { - console.log('JWT secret not provided. Skipping JWT strategy registration.'); - return; // Skip JWT registration if not configured - } - - const opts = { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: JWT_SECRET, - }; - - passport.use('jwt', new JwtStrategy(opts, (jwtPayload, done) => { - if (!jwtPayload) return done(null, false); - return done(null, jwtPayload); - })); - - console.log('JWT strategy registered successfully.'); -}; - -module.exports = { configure, type }; diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 2989cf65d..7a4c62635 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -65,8 +65,8 @@ async function validateJwt(token, authorityUrl, clientID, expectedAudience) { const jwtAuthHandler = () => { return async (req, res, next) => { - const authMethods = require('../../config').getAuthMethods(); - const jwtAuthMethod = authMethods.find((method) => method.type.toLowerCase() === "jwt"); + const apiAuthMethods = require('../../config').getAPIAuthMethods(); + const jwtAuthMethod = apiAuthMethods.find((method) => method.type.toLowerCase() === "jwt"); if (!jwtAuthMethod) { return next(); } From ec75f33e948d6237ec3a135144e70c512b120af4 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 29 May 2025 17:13:36 +0900 Subject: [PATCH 18/19] chore: set default empty config --- proxy.config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/proxy.config.json b/proxy.config.json index cf76bd982..618603a6a 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -56,13 +56,13 @@ }, { "type": "openidconnect", - "enabled": true, + "enabled": false, "oidcConfig": { - "issuer": "https://accounts.google.com/", - "clientID": "1009968223893-u92qq6itk7ej5008o4174gjubs5lhorg.apps.googleusercontent.com", - "clientSecret": "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", - "callbackURL": "http://localhost:8080/api/auth/oidc/callback", - "scope": "openid email profile" + "issuer": "", + "clientID": "", + "clientSecret": "", + "callbackURL": "", + "scope": "" } } ], @@ -151,8 +151,8 @@ "type": "jwt", "enabled": false, "jwtConfig": { - "clientID": "1009968223893-u92qq6itk7ej5008o4174gjubs5lhorg.apps.googleusercontent.com", - "authorityURL": "https://accounts.google.com" + "clientID": "", + "authorityURL": "" } } ], From 048446fd8332b54e46cba7f419dd3bc06206f9b9 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 29 May 2025 17:44:41 +0900 Subject: [PATCH 19/19] chore: clean up auth methods --- src/service/passport/activeDirectory.js | 6 ------ src/service/passport/jwtAuthHandler.js | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/service/passport/activeDirectory.js b/src/service/passport/activeDirectory.js index d5c6353d9..eef2f7826 100644 --- a/src/service/passport/activeDirectory.js +++ b/src/service/passport/activeDirectory.js @@ -31,11 +31,6 @@ const configure = (passport) => { profile.id = profile.username; req.user = profile; - console.log( - `passport.activeDirectory: resolved login ${ - profile._json.userPrincipalName - }, profile=${JSON.stringify(profile)}`, - ); // First check to see if the user is in the usergroups const isUser = await ldaphelper.isUserInAdGroup(profile.username, domain, userGroup); @@ -48,7 +43,6 @@ const configure = (passport) => { const isAdmin = await ldaphelper.isUserInAdGroup(profile.username, domain, adminGroup); profile.admin = isAdmin; - console.log(`passport.activeDirectory: ${profile.username} admin=${isAdmin}`); const user = { username: profile.username, diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 7a4c62635..da9e3bc47 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -1,6 +1,7 @@ const axios = require("axios"); const jwt = require("jsonwebtoken"); const jwkToPem = require("jwk-to-pem"); +const config = require('../../config'); /** * Obtain the JSON Web Key Set (JWKS) from the OIDC authority. @@ -65,7 +66,7 @@ async function validateJwt(token, authorityUrl, clientID, expectedAudience) { const jwtAuthHandler = () => { return async (req, res, next) => { - const apiAuthMethods = require('../../config').getAPIAuthMethods(); + const apiAuthMethods = config.getAPIAuthMethods(); const jwtAuthMethod = apiAuthMethods.find((method) => method.type.toLowerCase() === "jwt"); if (!jwtAuthMethod) { return next();