From 276fb83c2ee4f75844e1c7a21f7bf61eef3abd62 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Tue, 16 May 2023 12:22:25 +0000 Subject: [PATCH 01/13] Initial commit for oauth --- backend/config/default.cjs | 23 ++ backend/form.html | 30 +++ backend/package-lock.json | 475 ++++++++++++++++++++++++++++++++++-- backend/package.json | 3 + backend/src/routes.ts | 36 +++ backend/src/utils/config.ts | 9 + 6 files changed, 561 insertions(+), 15 deletions(-) create mode 100644 backend/form.html diff --git a/backend/config/default.cjs b/backend/config/default.cjs index 3e22006e3..102934125 100644 --- a/backend/config/default.cjs +++ b/backend/config/default.cjs @@ -141,6 +141,29 @@ module.exports = { ], }, + session: { + secret: '', + }, + + oauth: { + enabled: false, + + defaults: { + origin: '', + prefix: '/api/connect', + transport: 'session', + }, + + cognito: { + key: '', + secret: '', + dynamic: ['scope'], + response: ['tokens', 'raw', 'jwt'], + callback: '/', + subdomain: '', + }, + }, + // These settings are PUBLIC and shared with the UI ui: { // Show a banner at the top of the screen on all pages diff --git a/backend/form.html b/backend/form.html new file mode 100644 index 000000000..48b79afdf --- /dev/null +++ b/backend/form.html @@ -0,0 +1,30 @@ + + + + Grant - Dynamic HTTP Override + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + diff --git a/backend/package-lock.json b/backend/package-lock.json index ca204f35d..86a0ee712 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,13 +14,16 @@ "bunyan": "^1.8.15", "chalk": "^5.2.0", "config": "^3.3.9", + "connect-mongo": "^5.0.0", "cross-fetch": "^3.1.5", "dedent-js": "^1.0.1", "dev-null": "^0.1.1", "eslint-plugin-prettier": "^4.2.1", "express": "^5.0.0-beta.1", + "express-session": "^1.17.3", "form-data-encoder": "^2.1.4", "formdata-node": "^5.0.0", + "grant": "^5.4.21", "indent-string": "^5.0.0", "jsonschema": "^1.4.1", "jsonwebtoken": "^9.0.0", @@ -4949,6 +4952,22 @@ "proto-list": "~1.2.1" } }, + "node_modules/connect-mongo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.0.0.tgz", + "integrity": "sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg==", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=12.9.0" + }, + "peerDependencies": { + "express-session": "^1.17.1", + "mongodb": "^5.1.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "license": "MIT", @@ -6171,6 +6190,53 @@ "node": ">= 4" } }, + "node_modules/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "dependencies": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/debug": { "version": "3.1.0", "license": "MIT", @@ -6781,6 +6847,69 @@ "version": "4.2.11", "license": "ISC" }, + "node_modules/grant": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/grant/-/grant-5.4.21.tgz", + "integrity": "sha512-QaoZudI9Gmh2W415gd71Iul6gpVH9sG1SkjfnGHtqYZopQDQ5PUVxRol5zFCrwGi9S0EbExbelHlZScgdChg2w==", + "dependencies": { + "qs": "^6.10.2", + "request-compose": "^2.1.4", + "request-oauth": "^1.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "optionalDependencies": { + "cookie": "^0.4.1", + "cookie-signature": "^1.1.0", + "jwk-to-pem": "^2.0.5", + "jws": "^4.0.0" + } + }, + "node_modules/grant/node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "optional": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/grant/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/grant/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/grant/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/grapheme-splitter": { "version": "1.0.4", "license": "MIT" @@ -8831,6 +8960,17 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "optional": true, + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jws": { "version": "3.2.2", "license": "MIT", @@ -8861,6 +9001,17 @@ "node": ">=6" } }, + "node_modules/kruptein": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", + "integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/kubernetes-client": { "version": "9.0.0", "license": "MIT", @@ -9727,19 +9878,36 @@ } }, "node_modules/mongodb": { - "version": "4.16.0", - "license": "Apache-2.0", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", + "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", + "peer": true, "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", + "bson": "^5.3.0", + "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, "engines": { - "node": ">=12.9.0" + "node": ">=14.20.1" }, "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } } }, "node_modules/mongodb-connection-string-url": { @@ -9798,6 +9966,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", + "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "dev": true, + "dependencies": { + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.5.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", + "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", + "peer": true, + "engines": { + "node": ">=14.20.1" + } + }, "node_modules/mongoose": { "version": "6.11.0", "license": "MIT", @@ -9825,6 +10020,23 @@ "mongoose": "4.x || 5.x || 6.x" } }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", + "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "dependencies": { + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.5.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "license": "MIT" @@ -10217,7 +10429,6 @@ }, "node_modules/object-inspect": { "version": "1.12.3", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10947,6 +11158,14 @@ ], "license": "MIT" }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "license": "MIT", @@ -11058,6 +11277,35 @@ "node": ">= 6" } }, + "node_modules/request-compose": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/request-compose/-/request-compose-2.1.6.tgz", + "integrity": "sha512-S07L+2VbJB32WddD/o/PnYGKym63zLVbymygVWXvt8L79VAngcjAxhHaGuFOICLxEV90EasEPzqPKKHPspXP8w==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/request-oauth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/request-oauth/-/request-oauth-1.0.1.tgz", + "integrity": "sha512-85THTg1RgOYtqQw42JON6AqvHLptlj1biw265Tsq4fD4cPdUvhDB2Qh9NTv17yCD322ROuO9aOmpc4GyayGVBA==", + "dependencies": { + "oauth-sign": "^0.9.0", + "qs": "^6.9.6", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/request-oauth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "license": "MIT", @@ -11411,7 +11659,6 @@ }, "node_modules/side-channel": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.0", @@ -12180,6 +12427,17 @@ "node": ">=0.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "dev": true, @@ -16259,6 +16517,15 @@ "proto-list": "~1.2.1" } }, + "connect-mongo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.0.0.tgz", + "integrity": "sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg==", + "requires": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + } + }, "content-disposition": { "version": "0.5.4", "requires": { @@ -17057,6 +17324,46 @@ } } }, + "express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "requires": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "ext": { "version": "1.7.0", "requires": { @@ -17428,6 +17735,57 @@ "graceful-fs": { "version": "4.2.11" }, + "grant": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/grant/-/grant-5.4.21.tgz", + "integrity": "sha512-QaoZudI9Gmh2W415gd71Iul6gpVH9sG1SkjfnGHtqYZopQDQ5PUVxRol5zFCrwGi9S0EbExbelHlZScgdChg2w==", + "requires": { + "cookie": "^0.4.1", + "cookie-signature": "^1.1.0", + "jwk-to-pem": "^2.0.5", + "jws": "^4.0.0", + "qs": "^6.10.2", + "request-compose": "^2.1.4", + "request-oauth": "^1.0.1" + }, + "dependencies": { + "cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "optional": true + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, "grapheme-splitter": { "version": "1.0.4" }, @@ -18701,6 +19059,17 @@ "safe-buffer": "^5.0.1" } }, + "jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "optional": true, + "requires": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, "jws": { "version": "3.2.2", "requires": { @@ -18721,6 +19090,14 @@ "version": "3.0.3", "dev": true }, + "kruptein": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", + "integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==", + "requires": { + "asn1.js": "^5.4.1" + } + }, "kubernetes-client": { "version": "9.0.0", "requires": { @@ -19379,13 +19756,23 @@ "optional": true }, "mongodb": { - "version": "4.16.0", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", + "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", + "peer": true, "requires": { - "@aws-sdk/credential-providers": "^3.186.0", - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", + "bson": "^5.3.0", + "mongodb-connection-string-url": "^2.6.0", "saslprep": "^1.0.3", "socks": "^2.7.1" + }, + "dependencies": { + "bson": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", + "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", + "peer": true + } } }, "mongodb-connection-string-url": { @@ -19426,6 +19813,19 @@ "camelcase": { "version": "6.3.0", "dev": true + }, + "mongodb": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", + "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "dev": true, + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.5.4", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } } } }, @@ -19441,6 +19841,18 @@ "sift": "16.0.1" }, "dependencies": { + "mongodb": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", + "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.5.4", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, "ms": { "version": "2.1.3" } @@ -19679,8 +20091,7 @@ "version": "2.2.0" }, "object-inspect": { - "version": "1.12.3", - "dev": true + "version": "1.12.3" }, "oidc-token-hash": { "version": "5.0.3" @@ -20098,6 +20509,11 @@ "queue-microtask": { "version": "1.2.3" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + }, "randombytes": { "version": "2.1.0", "requires": { @@ -20194,6 +20610,28 @@ } } }, + "request-compose": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/request-compose/-/request-compose-2.1.6.tgz", + "integrity": "sha512-S07L+2VbJB32WddD/o/PnYGKym63zLVbymygVWXvt8L79VAngcjAxhHaGuFOICLxEV90EasEPzqPKKHPspXP8w==" + }, + "request-oauth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/request-oauth/-/request-oauth-1.0.1.tgz", + "integrity": "sha512-85THTg1RgOYtqQw42JON6AqvHLptlj1biw265Tsq4fD4cPdUvhDB2Qh9NTv17yCD322ROuO9aOmpc4GyayGVBA==", + "requires": { + "oauth-sign": "^0.9.0", + "qs": "^6.9.6", + "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "require-directory": { "version": "2.1.1" }, @@ -20388,7 +20826,6 @@ }, "side-channel": { "version": "1.0.4", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -20844,6 +21281,14 @@ "uglify-js": { "version": "3.17.4" }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "undefsafe": { "version": "2.0.5", "dev": true diff --git a/backend/package.json b/backend/package.json index d58f576e3..45e664d4e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -35,13 +35,16 @@ "bunyan": "^1.8.15", "chalk": "^5.2.0", "config": "^3.3.9", + "connect-mongo": "^5.0.0", "cross-fetch": "^3.1.5", "dedent-js": "^1.0.1", "dev-null": "^0.1.1", "eslint-plugin-prettier": "^4.2.1", "express": "^5.0.0-beta.1", + "express-session": "^1.17.3", "form-data-encoder": "^2.1.4", "formdata-node": "^5.0.0", + "grant": "^5.4.21", "indent-string": "^5.0.0", "jsonschema": "^1.4.1", "jsonwebtoken": "^9.0.0", diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 93c09f726..8f7d977d4 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1,4 +1,10 @@ +import parser from 'body-parser' +import MongoStore from 'connect-mongo' import express from 'express' +import session from 'express-session' +import fs from 'fs' +import grant from 'grant' +import { omit } from 'lodash-es' import { getApplicationLogs, getItemLogs } from './routes/v1/admin.js' import { getApprovals, getNumApprovals, postApprovalResponse } from './routes/v1/approvals.js' @@ -38,6 +44,7 @@ import { putUpdateLastViewed, putVersion, } from './routes/v1/version.js' +import config from './utils/config.js' import { expressErrorHandler, expressLogger } from './utils/logger.js' import { getUser } from './utils/user.js' @@ -46,6 +53,35 @@ export const server = express() server.use(getUser) server.use(expressLogger) +if (config.oauth.enabled) { + server.use( + session({ + secret: config.session.secret, + resave: true, + saveUninitialized: true, + cookie: { maxAge: 30 * 24 * 60 * 60000 }, // store for 30 days + // store: MongoStore.create({ + // mongoUrl: config.mongo.uri, + // }), + }) + ) + + server.use(parser.urlencoded({ extended: true })) + + console.log(omit(config.oauth, 'enabled')) + + server.use(grant.default.express(omit(config.oauth, 'enabled'))) + + server.get('/api/login', (req, res) => { + res.writeHead(200, { 'content-type': 'text/html' }) + res.end(fs.readFileSync('./form.html', 'utf8')) + }) + + server.get('/api/hello', (req, res) => { + res.end(JSON.stringify((req as any).session.grant.response, null, 2)) + }) +} + server.post('/api/v1/model', ...postUpload) server.get('/api/v1/models', ...getModels) diff --git a/backend/src/utils/config.ts b/backend/src/utils/config.ts index bed68d72d..f8b5733f0 100644 --- a/backend/src/utils/config.ts +++ b/backend/src/utils/config.ts @@ -107,6 +107,12 @@ export interface Config { deployment: Array } + oauth: unknown & { enabled: boolean } + + session: { + secret: string + } + ui: { banner: { enabled: boolean @@ -210,6 +216,9 @@ const config: Config = { defaultSchemas: _config.get('defaultSchemas'), + oauth: _config.get('oauth'), + session: _config.get('session'), + ui: _config.get('ui'), } From 77f65dd6ce12e116003b265a75a5f909cc912be7 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Tue, 16 May 2023 14:45:08 +0000 Subject: [PATCH 02/13] Update backend oauth implementation --- backend/form.html | 30 -------------- backend/global.d.ts | 4 ++ backend/src/external/Authorisation.ts | 4 +- .../Authorisation/AuthorisationDefault.ts} | 6 +-- .../Authorisation/AuthorisationOAuth.ts | 39 +++++++++++++++++++ backend/src/routes.ts | 24 ++++++------ backend/src/routes/v1/approvals.ts | 10 ++--- backend/src/routes/v1/deployment.ts | 8 ++-- backend/src/utils/user.ts | 4 +- frontend/utils/fetcher.ts | 4 ++ 10 files changed, 74 insertions(+), 59 deletions(-) delete mode 100644 backend/form.html rename backend/src/{utils/AuthorisationBase.ts => external/Authorisation/AuthorisationDefault.ts} (86%) create mode 100644 backend/src/external/Authorisation/AuthorisationOAuth.ts diff --git a/backend/form.html b/backend/form.html deleted file mode 100644 index 48b79afdf..000000000 --- a/backend/form.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - Grant - Dynamic HTTP Override - - -
-
-
- -
-
- -
-
- -
-
- -
-
-
- - diff --git a/backend/global.d.ts b/backend/global.d.ts index f6b7e4b5b..43580b81a 100644 --- a/backend/global.d.ts +++ b/backend/global.d.ts @@ -6,6 +6,10 @@ declare namespace Express { reqId: string log: Logger + + session: { + grant: any + } } interface Response { diff --git a/backend/src/external/Authorisation.ts b/backend/src/external/Authorisation.ts index 972954d9e..6986829ae 100644 --- a/backend/src/external/Authorisation.ts +++ b/backend/src/external/Authorisation.ts @@ -4,8 +4,8 @@ // TypeScript will ensure at build time that any updates made to the // Authorisation layout are reflected in your class. -import AuthorisationBase from '../utils/AuthorisationBase.js' +import AuthorisationOAuth from './Authorisation/AuthorisationOAuth.js' -class Authorisation extends AuthorisationBase {} +class Authorisation extends AuthorisationOAuth {} export default Authorisation diff --git a/backend/src/utils/AuthorisationBase.ts b/backend/src/external/Authorisation/AuthorisationDefault.ts similarity index 86% rename from backend/src/utils/AuthorisationBase.ts rename to backend/src/external/Authorisation/AuthorisationDefault.ts index d60d8f5f0..00ae34a09 100644 --- a/backend/src/utils/AuthorisationBase.ts +++ b/backend/src/external/Authorisation/AuthorisationDefault.ts @@ -1,9 +1,9 @@ import { Request } from 'express' -import { DeploymentDoc, ModelDoc, UserDoc, VersionDoc } from '../types/types.js' -import { Model } from '../types/types.js' +import { DeploymentDoc, ModelDoc, UserDoc, VersionDoc } from '../../types/types.js' +import { Model } from '../../types/types.js' -export default class AuthorisationBase { +export default class AuthorisationDefault { async getUserFromReq(req: Request) { const userId = 'user' const email = 'user@example.com' diff --git a/backend/src/external/Authorisation/AuthorisationOAuth.ts b/backend/src/external/Authorisation/AuthorisationOAuth.ts new file mode 100644 index 000000000..3f5a6bf8d --- /dev/null +++ b/backend/src/external/Authorisation/AuthorisationOAuth.ts @@ -0,0 +1,39 @@ +import { Request } from 'express' + +import { DeploymentDoc, ModelDoc, UserDoc, VersionDoc } from '../../types/types.js' +import { Model } from '../../types/types.js' + +export default class AuthorisationOAuth { + async getUserFromReq(req: Request) { + console.log(req.session) + + if (!req.session?.grant?.response?.jwt) { + return {} + } + + const userId = req.session.grant.response.jwt.id_token.payload['identities'][0].userId + const email = userId + + const data = {} + const roles = ['user'] + + return { + userId, + email, + data, + roles, + } + } + + async canUserSeeModel(_user: UserDoc, _model: Model | ModelDoc) { + return true + } + + async canUserSeeVersion(_user: UserDoc, _model: VersionDoc) { + return true + } + + async canUserSeeDeployment(_user: UserDoc, _deployment: DeploymentDoc) { + return true + } +} diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 8f7d977d4..455068bae 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -50,9 +50,6 @@ import { getUser } from './utils/user.js' export const server = express() -server.use(getUser) -server.use(expressLogger) - if (config.oauth.enabled) { server.use( session({ @@ -60,25 +57,26 @@ if (config.oauth.enabled) { resave: true, saveUninitialized: true, cookie: { maxAge: 30 * 24 * 60 * 60000 }, // store for 30 days - // store: MongoStore.create({ - // mongoUrl: config.mongo.uri, - // }), + store: MongoStore.create({ + mongoUrl: config.mongo.uri, + }), }) ) +} - server.use(parser.urlencoded({ extended: true })) - - console.log(omit(config.oauth, 'enabled')) +server.use(getUser) +server.use(expressLogger) +if (config.oauth.enabled) { + server.use(parser.urlencoded({ extended: true })) server.use(grant.default.express(omit(config.oauth, 'enabled'))) server.get('/api/login', (req, res) => { - res.writeHead(200, { 'content-type': 'text/html' }) - res.end(fs.readFileSync('./form.html', 'utf8')) + res.redirect('/api/connect/cognito/login') }) - server.get('/api/hello', (req, res) => { - res.end(JSON.stringify((req as any).session.grant.response, null, 2)) + server.get('/api/grant', (req, res) => { + res.end(JSON.stringify(req.session.grant.response, null, 2)) }) } diff --git a/backend/src/routes/v1/approvals.ts b/backend/src/routes/v1/approvals.ts index 150a2a8c8..50f9f2d44 100644 --- a/backend/src/routes/v1/approvals.ts +++ b/backend/src/routes/v1/approvals.ts @@ -18,7 +18,7 @@ import { reviewedApproval } from '../../templates/reviewedApproval.js' import { ApprovalCategory, ApprovalStates, DeploymentDoc, Entity, ModelDoc, VersionDoc } from '../../types/types.js' import { getUserListFromEntityList, isUserInEntityList } from '../../utils/entity.js' import { getDeploymentQueue } from '../../utils/queues.js' -import { BadReq, Unauthorised } from '../../utils/result.js' +import { BadReq, Forbidden } from '../../utils/result.js' import { sendEmail } from '../../utils/smtp.js' import { ensureUserRole, hasRole } from '../../utils/user.js' @@ -37,10 +37,10 @@ export const getApprovals = [ if (filter === 'all') { if (!hasRole(['admin'], req.user)) { - return res.error(401, [ + throw Forbidden( { code: 'unauthorised_admin_role_missing', roles: req.user.roles }, - 'Forbidden. Your user does not have the "admin" role', - ]) + 'Forbidden. Your user does not have the "admin" role' + ) } } else { req.log.info({ code: 'fetching_user_approvals' }, 'Getting approvals for user') @@ -83,7 +83,7 @@ export const postApprovalResponse = [ const approval = await getApproval({ approvalId: id }) if (!(await isUserInEntityList(req.user, approval.approvers)) && !hasRole(['admin'], req.user)) { - throw Unauthorised( + throw Forbidden( { code: 'unauthorised_to_approve', approvalId: id, diff --git a/backend/src/routes/v1/deployment.ts b/backend/src/routes/v1/deployment.ts index 657e01f8a..fb23931a4 100644 --- a/backend/src/routes/v1/deployment.ts +++ b/backend/src/routes/v1/deployment.ts @@ -1,4 +1,3 @@ -import { ModelDoc } from '../../types/types.js' import bodyParser from 'body-parser' import { Request, Response } from 'express' import { customAlphabet } from 'nanoid' @@ -8,11 +7,12 @@ import { createDeployment, findDeploymentByUuid, findDeployments } from '../../s import { findModelByUuid } from '../../services/model.js' import { findSchemaByRef } from '../../services/schema.js' import { findVersionById, findVersionByName } from '../../services/version.js' +import { ModelDoc } from '../../types/types.js' import { ApprovalStates, EntityKind } from '../../types/types.js' import config from '../../utils/config.js' import { isUserInEntityList, parseEntityList } from '../../utils/entity.js' import { getClient } from '../../utils/minio.js' -import { BadReq, Forbidden, NotFound, Unauthorised } from '../../utils/result.js' +import { BadReq, Forbidden, NotFound } from '../../utils/result.js' import { ensureUserRole } from '../../utils/user.js' import { validateSchema } from '../../utils/validateSchema.js' @@ -248,7 +248,7 @@ export const fetchRawModelFiles = [ if (!(await isUserInEntityList(req.user, deployment.metadata.contacts.owner))) { const owners = deployment.metadata.contacts.owner.map((owner: any) => owner.id).join(', ') - throw Unauthorised( + throw Forbidden( { deploymentOwner: deployment.metadata.contacts.owner }, `User is not authorised to download this file. Requester: ${req.user.id}, owners: ${owners}` ) @@ -261,7 +261,7 @@ export const fetchRawModelFiles = [ } if (deployment.managerApproved !== 'Accepted') { - throw Unauthorised( + throw Forbidden( { approvalStatus: deployment.managerApproved }, 'User is not authorised to download this file as it has not been approved.' ) diff --git a/backend/src/utils/user.ts b/backend/src/utils/user.ts index 1e8feb064..73ce78ec7 100644 --- a/backend/src/utils/user.ts +++ b/backend/src/utils/user.ts @@ -89,14 +89,14 @@ export function hasRole(roles: Array | string, user: UserDoc) { export function ensureUserRole(roles: Array | string) { return function ensureUserRoleMiddleware(req: Request, _res: Response, next: NextFunction) { if (!req.user) { - throw Forbidden({}, `Unable to authenticate request`) + throw Unauthorised({}, `Unable to authenticate request`) } const arrayRoles = typeof roles === 'string' ? [roles] : roles for (const role of arrayRoles) { if (!req.user.roles.includes(role)) { - throw Unauthorised({ requestedRole: role, currentRoles: req.user.roles }, `You do not have the '${role}' role`) + throw Forbidden({ requestedRole: role, currentRoles: req.user.roles }, `You do not have the '${role}' role`) } } diff --git a/frontend/utils/fetcher.ts b/frontend/utils/fetcher.ts index 0b814b72c..18bea5394 100644 --- a/frontend/utils/fetcher.ts +++ b/frontend/utils/fetcher.ts @@ -29,6 +29,10 @@ export const fetcher = async (input: RequestInfo, init: RequestInit) => { // If the status code is not in the range 200-299, // we still try to parse and throw it. if (!res.ok) { + if (res.status === 401) { + window.location.href = '/api/login' + } + const error: ErrorInfo = { ...new Error('An error occurred while fetching the data.'), info: await res.json(), From f1cde19144611c2ae2cf3f9958534c602dc4555c Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Tue, 16 May 2023 15:04:37 +0000 Subject: [PATCH 03/13] Tidy up grant config --- backend/config/default.cjs | 31 ++++++++++++++++++------------- backend/global.d.ts | 1 + backend/src/routes.ts | 12 ++++++------ backend/src/utils/config.ts | 7 ++++++- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/backend/config/default.cjs b/backend/config/default.cjs index 102934125..8ba6fc89d 100644 --- a/backend/config/default.cjs +++ b/backend/config/default.cjs @@ -147,20 +147,25 @@ module.exports = { oauth: { enabled: false, + provider: 'cognito', + + grant: { + // Grant configuration options, provide any option from: + // https://www.npmjs.com/package/grant + defaults: { + origin: '', + prefix: '/api/connect', + transport: 'session', + }, - defaults: { - origin: '', - prefix: '/api/connect', - transport: 'session', - }, - - cognito: { - key: '', - secret: '', - dynamic: ['scope'], - response: ['tokens', 'raw', 'jwt'], - callback: '/', - subdomain: '', + cognito: { + key: '', + secret: '', + dynamic: ['scope'], + response: ['tokens', 'raw', 'jwt'], + callback: '/', + subdomain: '', + }, }, }, diff --git a/backend/global.d.ts b/backend/global.d.ts index 43580b81a..ca5f0ca9c 100644 --- a/backend/global.d.ts +++ b/backend/global.d.ts @@ -8,6 +8,7 @@ declare namespace Express { log: Logger session: { + destroy: Function grant: any } } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 455068bae..c44dc273c 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -2,9 +2,7 @@ import parser from 'body-parser' import MongoStore from 'connect-mongo' import express from 'express' import session from 'express-session' -import fs from 'fs' import grant from 'grant' -import { omit } from 'lodash-es' import { getApplicationLogs, getItemLogs } from './routes/v1/admin.js' import { getApprovals, getNumApprovals, postApprovalResponse } from './routes/v1/approvals.js' @@ -69,14 +67,16 @@ server.use(expressLogger) if (config.oauth.enabled) { server.use(parser.urlencoded({ extended: true })) - server.use(grant.default.express(omit(config.oauth, 'enabled'))) + server.use(grant.default.express(config.oauth.grant)) server.get('/api/login', (req, res) => { - res.redirect('/api/connect/cognito/login') + res.redirect(`/api/connect/${config.oauth.provider}/login`) }) - server.get('/api/grant', (req, res) => { - res.end(JSON.stringify(req.session.grant.response, null, 2)) + server.get('/api/logout', (req, res) => { + req.session.destroy(function (err) { + res.redirect('/') + }) }) } diff --git a/backend/src/utils/config.ts b/backend/src/utils/config.ts index f8b5733f0..0a22cfb25 100644 --- a/backend/src/utils/config.ts +++ b/backend/src/utils/config.ts @@ -1,4 +1,5 @@ import _config from 'config' +import { GrantConfig, GrantOptions } from 'grant' interface DefaultSchema { name: string @@ -107,7 +108,11 @@ export interface Config { deployment: Array } - oauth: unknown & { enabled: boolean } + oauth: { + enabled: boolean + provider: string + grant: GrantOptions | GrantConfig + } session: { secret: string From 90064b9e0e33a017e8022dc8c727afe94ba4fd7a Mon Sep 17 00:00:00 2001 From: ARADDCC012 <110473008+ARADDCC012@users.noreply.github.com> Date: Tue, 16 May 2023 15:29:50 +0000 Subject: [PATCH 04/13] Added UI login redirects --- frontend/data/api.ts | 10 +++++++++- frontend/src/Wrapper.tsx | 9 +++++++++ frontend/utils/fetcher.ts | 8 ++++++-- frontend/utils/loginUtils.ts | 3 +++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 frontend/utils/loginUtils.ts diff --git a/frontend/data/api.ts b/frontend/data/api.ts index 2f233e631..b35f88c4e 100644 --- a/frontend/data/api.ts +++ b/frontend/data/api.ts @@ -1,5 +1,7 @@ +import { redirectToLoginPage } from 'utils/loginUtils' + export async function fetchEndpoint(url: string, method: string, data?: unknown) { - return fetch(url, { + const response = await fetch(url, { method, headers: { ...(!data && data !== 0 && { 'Content-Length': '0' }), @@ -7,6 +9,12 @@ export async function fetchEndpoint(url: string, method: string, data?: unknown) }, ...(data !== undefined && { body: JSON.stringify(data) }), }) + + if (response.status === 401) { + redirectToLoginPage() + } + + return response } export async function getEndpoint(url: string) { diff --git a/frontend/src/Wrapper.tsx b/frontend/src/Wrapper.tsx index e14131edd..3a20faa77 100644 --- a/frontend/src/Wrapper.tsx +++ b/frontend/src/Wrapper.tsx @@ -6,6 +6,7 @@ import DashboardIcon from '@mui/icons-material/DashboardTwoTone' import FileUploadIcon from '@mui/icons-material/FileUploadTwoTone' import LinkIcon from '@mui/icons-material/LinkTwoTone' import ListAltIcon from '@mui/icons-material/ListAlt' +import LogoutIcon from '@mui/icons-material/LogoutTwoTone' import MenuIcon from '@mui/icons-material/MenuTwoTone' import SchemaIcon from '@mui/icons-material/SchemaTwoTone' import Settings from '@mui/icons-material/SettingsTwoTone' @@ -244,6 +245,14 @@ export default function Wrapper({ title, page, children }: WrapperProps): ReactE Settings + + + + + + Sign Out + + diff --git a/frontend/utils/fetcher.ts b/frontend/utils/fetcher.ts index 18bea5394..470d62d99 100644 --- a/frontend/utils/fetcher.ts +++ b/frontend/utils/fetcher.ts @@ -1,3 +1,5 @@ +import { redirectToLoginPage } from 'utils/loginUtils' + export type ErrorInfo = Error & { info: { message: string @@ -12,6 +14,9 @@ export const textFetcher = async (input: RequestInfo, init: RequestInit) => { // If the status code is not in the range 200-299, // we still try to parse and throw it. if (!res.ok) { + if (res.status === 401) { + redirectToLoginPage() + } const error: ErrorInfo = { ...new Error('An error occurred while fetching the data.'), info: await res.json(), @@ -30,9 +35,8 @@ export const fetcher = async (input: RequestInfo, init: RequestInit) => { // we still try to parse and throw it. if (!res.ok) { if (res.status === 401) { - window.location.href = '/api/login' + redirectToLoginPage() } - const error: ErrorInfo = { ...new Error('An error occurred while fetching the data.'), info: await res.json(), diff --git a/frontend/utils/loginUtils.ts b/frontend/utils/loginUtils.ts new file mode 100644 index 000000000..9085c497a --- /dev/null +++ b/frontend/utils/loginUtils.ts @@ -0,0 +1,3 @@ +export const redirectToLoginPage = () => { + window.location.href = '/api/login' +} From 626134ae786a1ebde4e5e4713194bea21ba46696 Mon Sep 17 00:00:00 2001 From: JR40159 <126243293+JR40159@users.noreply.github.com> Date: Thu, 18 May 2023 08:54:17 +0000 Subject: [PATCH 05/13] Fix backend build issues --- backend/src/external/Authorisation/AuthorisationOAuth.ts | 7 ++++++- backend/src/services/logs.ts | 2 +- backend/src/services/user.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/src/external/Authorisation/AuthorisationOAuth.ts b/backend/src/external/Authorisation/AuthorisationOAuth.ts index 3f5a6bf8d..8f92b683f 100644 --- a/backend/src/external/Authorisation/AuthorisationOAuth.ts +++ b/backend/src/external/Authorisation/AuthorisationOAuth.ts @@ -8,7 +8,12 @@ export default class AuthorisationOAuth { console.log(req.session) if (!req.session?.grant?.response?.jwt) { - return {} + return { + userId: undefined, + email: undefined , + data: undefined, + roles: undefined, + } } const userId = req.session.grant.response.jwt.id_token.payload['identities'][0].userId diff --git a/backend/src/services/logs.ts b/backend/src/services/logs.ts index 317a40f5a..d8daef04a 100644 --- a/backend/src/services/logs.ts +++ b/backend/src/services/logs.ts @@ -58,7 +58,7 @@ export interface GetLogsArgs { buildId?: string approvalId?: string } -export async function getLogs({ after, before, level, types, search, isRegex, buildId, approvalId }: GetLogsArgs) { +export async function getLogs({ after, before, level, types, search, isRegex, buildId, approvalId }: GetLogsArgs): Promise { await connectToMongoose() const { db } = mongoose.connection diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index cfcffc846..f7f6f2bb1 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -30,7 +30,7 @@ interface FindAndUpdateUserArgs { userId?: string email?: string data?: any - roles: Array + roles?: Array } export async function findAndUpdateUser({ userId, email, data, roles }: FindAndUpdateUserArgs) { From 2a367ad2a9254557bd3864c77da9edcea4014290 Mon Sep 17 00:00:00 2001 From: ARADDCC013 <111884477+ARADDCC013@users.noreply.github.com> Date: Fri, 19 May 2023 07:37:29 +0000 Subject: [PATCH 06/13] update helm oauth --- .../templates/bailo/bailo.configmap.yaml | 19 +++++++++++++++++++ infrastructure/helm/bailo/values.yaml | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/infrastructure/helm/bailo/templates/bailo/bailo.configmap.yaml b/infrastructure/helm/bailo/templates/bailo/bailo.configmap.yaml index 80590dcfc..ce47c2236 100644 --- a/infrastructure/helm/bailo/templates/bailo/bailo.configmap.yaml +++ b/infrastructure/helm/bailo/templates/bailo/bailo.configmap.yaml @@ -22,6 +22,25 @@ data: s2i: { path: '/s2i/s2i', }, + session: { + secret: '{{ .Values.cookie.secret }}', + }, + oauth: { + enabled: {{ .Values.oauth.enabled }}, + grant: { + defaults: { + origin: '{{ .Values.oauth.origin }}', + }, + cognito: { + key: '{{ .Values.oauth.cognito.key }}', + secret: '{{ .Values.oauth.cognito.secret }}', + dynamic: {{ .Values.oauth.cognito.dynamic }}, + response: {{ .Values.oauth.cognito.response }}, + callback: '{{ .Values.oauth.cognito.callback }}', + subdomain: '{{ .Values.oauth.cognito.subdomain }}', + }, + }, + }, build: { environment: '{{ .Values.config.build.environment }}', openshift: { diff --git a/infrastructure/helm/bailo/values.yaml b/infrastructure/helm/bailo/values.yaml index b53cda154..1aa6ebd4d 100644 --- a/infrastructure/helm/bailo/values.yaml +++ b/infrastructure/helm/bailo/values.yaml @@ -63,6 +63,22 @@ route: enabled: false # false for k8s | aws appPublicRoute: "route-url" # can also be used for aws loadbalancer url +# Oauth | Congnito +# using bash for generating cookie secret example +# dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo +cookie: + secret: 'somerandomstring12341234567890AB' +oauth: + enabled: false + origin: 'bailo fqdn' + cognito: + key: 'cognito app client id' + secret: 'cognito app client secret' + dynamic: [] # example 'scope' + response: [] #example 'tokens', 'raw', 'jwt' + callback: '' # exmaple / + subdomain: 'cognito domain' + # Used for aws storage aws: enabled: false From 6c43176dadedd3026d2c4f118587cadd03b8f556 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:43:21 +0000 Subject: [PATCH 07/13] Default to stupid authorisation --- backend/src/external/Authorisation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/external/Authorisation.ts b/backend/src/external/Authorisation.ts index 6986829ae..76e70b0b1 100644 --- a/backend/src/external/Authorisation.ts +++ b/backend/src/external/Authorisation.ts @@ -4,8 +4,8 @@ // TypeScript will ensure at build time that any updates made to the // Authorisation layout are reflected in your class. -import AuthorisationOAuth from './Authorisation/AuthorisationOAuth.js' +import AuthorisationDefault from './Authorisation/AuthorisationDefault' -class Authorisation extends AuthorisationOAuth {} +class Authorisation extends AuthorisationDefault {} export default Authorisation From b78d9be75833149fa5f9c3e9972dbead51b3752c Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:51:48 +0000 Subject: [PATCH 08/13] Fix OAuth import --- backend/src/external/Authorisation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/external/Authorisation.ts b/backend/src/external/Authorisation.ts index 76e70b0b1..a71e792ce 100644 --- a/backend/src/external/Authorisation.ts +++ b/backend/src/external/Authorisation.ts @@ -4,7 +4,7 @@ // TypeScript will ensure at build time that any updates made to the // Authorisation layout are reflected in your class. -import AuthorisationDefault from './Authorisation/AuthorisationDefault' +import AuthorisationDefault from './Authorisation/AuthorisationDefault.js' class Authorisation extends AuthorisationDefault {} From e1837e53ed1fef1cb3240c5f6fb7ab32aa3e84c9 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:57:58 +0000 Subject: [PATCH 09/13] Fix OAuth styling --- backend/src/external/Authorisation/AuthorisationOAuth.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/external/Authorisation/AuthorisationOAuth.ts b/backend/src/external/Authorisation/AuthorisationOAuth.ts index 8f92b683f..1bcb2dacc 100644 --- a/backend/src/external/Authorisation/AuthorisationOAuth.ts +++ b/backend/src/external/Authorisation/AuthorisationOAuth.ts @@ -9,10 +9,10 @@ export default class AuthorisationOAuth { if (!req.session?.grant?.response?.jwt) { return { - userId: undefined, - email: undefined , - data: undefined, - roles: undefined, + userId: undefined, + email: undefined, + data: undefined, + roles: undefined, } } From e61a8fd516a58f70b66ea47c18b1dd515843a755 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:58:08 +0000 Subject: [PATCH 10/13] Fix tsconfig styling --- backend/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 2da1cb15f..312496e23 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -10,7 +10,7 @@ "outDir": "dist", "resolveJsonModule": true, "noImplicitAny": false, - "composite": true, + "composite": true }, "include": ["global.d.ts", "src/**/*", "src/**/*.json", "package.json", "package-lock.json"], "ts-node": { From 9f89ce959673e30b7520bdc474033fc895e9f216 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 14:22:15 +0000 Subject: [PATCH 11/13] Define proper function type for session destroyer --- backend/global.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/global.d.ts b/backend/global.d.ts index ca5f0ca9c..4331d52bb 100644 --- a/backend/global.d.ts +++ b/backend/global.d.ts @@ -8,7 +8,7 @@ declare namespace Express { log: Logger session: { - destroy: Function + destroy: () => void grant: any } } From 4d1eb3d515d427a8a05ce31bb1e5680f59c019e1 Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:08:12 +0000 Subject: [PATCH 12/13] Use timeout instead of javascript function --- frontend/cypress/e2e/model.cy.ts | 13 ++++++++----- frontend/cypress/scripts/pullContainer.sh | 3 +++ 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100755 frontend/cypress/scripts/pullContainer.sh diff --git a/frontend/cypress/e2e/model.cy.ts b/frontend/cypress/e2e/model.cy.ts index b379a6548..42bcb282d 100644 --- a/frontend/cypress/e2e/model.cy.ts +++ b/frontend/cypress/e2e/model.cy.ts @@ -110,11 +110,11 @@ describe('Model with code and binary files', () => { cy.get('[data-test=submitButton]').click() cy.log('Checking URL has been updated') - cy.fixture('deployment.json').then((deploymentMetadata) => { + cy.fixture('deployment.json').then({ timeout: 60000 }, (deploymentMetadata) => { cy.url({ timeout: 10000 }) .as('deploymentUrl') .should('contain', `/deployment/${convertNameToUrlFormat(deploymentMetadata.highLevelDetails.name)}`) - .then(async (url) => { + .then({ timeout: 60000 }, async (url) => { deploymentUuid = getUuidFromUrl(url) cy.log('Navigating to review page') @@ -143,14 +143,17 @@ describe('Model with code and binary files', () => { cy.get('[data-test=dockerPassword]').should('not.contain.text', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx') cy.get('[data-test=dockerPassword]') .invoke('text') - .then((dockerPassword) => { - cy.fixture('minimal_metadata.json').then((modelMetadata) => { + .then({ timeout: 60000 }, (dockerPassword) => { + cy.fixture('minimal_metadata.json').then({ timeout: 60000 }, async (modelMetadata) => { const imageName = `${registryUrl}/${deploymentUuid}/${modelUuid}:${modelMetadata.highLevelDetails.modelCardVersion}` cy.exec(`docker login ${registryUrl} -u ${'user'} -p ${dockerPassword}`) - cy.exec(`docker pull ${imageName}`) + + cy.exec(`cypress/scripts/pullContainer.sh "${imageName}"`) + cy.exec(`cypress/scripts/startContainer.sh "${imageName}"`) // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(5000) + cy.request('POST', `${containerUrl}/predict`, { jsonData: { data: ['should be returned backwards'] }, }).then((response) => { diff --git a/frontend/cypress/scripts/pullContainer.sh b/frontend/cypress/scripts/pullContainer.sh new file mode 100755 index 000000000..949968e96 --- /dev/null +++ b/frontend/cypress/scripts/pullContainer.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +timeout 2m bash -c "until docker pull $1; do sleep 1; done" \ No newline at end of file From b72ebc03e80187628dcc6daa4db0c13508c35c2d Mon Sep 17 00:00:00 2001 From: a3957273 <89583054+a3957273@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:15:30 +0000 Subject: [PATCH 13/13] Add callback type --- backend/global.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/global.d.ts b/backend/global.d.ts index 4331d52bb..e02469319 100644 --- a/backend/global.d.ts +++ b/backend/global.d.ts @@ -1,5 +1,7 @@ declare module 'dev-null' +type callback = (err: string | undefined) => void + declare namespace Express { interface Request { user: UserDoc @@ -8,7 +10,7 @@ declare namespace Express { log: Logger session: { - destroy: () => void + destroy: (callback) => void grant: any } }