diff --git a/nesis/api/core/services/management.py b/nesis/api/core/services/management.py index 8531af0..55e77df 100644 --- a/nesis/api/core/services/management.py +++ b/nesis/api/core/services/management.py @@ -66,10 +66,14 @@ def create(self, **kwargs) -> UserSession: self.__LOG.debug(f"Received session object {kwargs}") email = user_session.get("email") password = user_session.get("password") + + # Hard code these to make it easier to test session_oauth_token_value = user_session.get( - os.environ.get("NESIS_OAUTH_TOKEN_KEY") + os.environ.get("NESIS_OAUTH_TOKEN_KEY") or "___nesis_oauth_token_key___" + ) + oauth_token_value = ( + os.environ.get("NESIS_OAUTH_TOKEN_VALUE") or "___nesis_oauth_token_value___" ) - oauth_token_value = os.environ.get("NESIS_OAUTH_TOKEN_VALUE") try: @@ -85,7 +89,6 @@ def create(self, **kwargs) -> UserSession: user_password = password.encode("utf-8") if not bcrypt.checkpw(user_password, db_pass): raise UnauthorizedAccess("Invalid email/password") - # update last login details. db_user = users[0] return self.__create_user_session(db_user) @@ -114,7 +117,7 @@ def create(self, **kwargs) -> UserSession: def __create_user_session(self, db_user: User): user_dict = db_user.to_dict() - token = SG("[\l\d]{128}").render() + token = SG(r"[\l\d]{128}").render() session_token = self.__cache_key(token) expiry = ( self.__config["memcache"].get("session", {"expiry": 0}).get("expiry", 0) @@ -122,7 +125,7 @@ def __create_user_session(self, db_user: User): if self.__cache.get(session_token) is None: self.__cache.set(session_token, user_dict, time=expiry) while self.__cache.get(session_token)["id"] != user_dict["id"]: - token = SG("[\l\d]{128}").render() + token = SG(r"[\l\d]{128}").render() session_token = self.__cache_key(token) self.__cache.set(session_token, user_dict, time=expiry) return UserSession(token=token, expiry=expiry, user=db_user) diff --git a/nesis/frontend/client/package-lock.json b/nesis/frontend/client/package-lock.json index 6b60314..4dabe38 100644 --- a/nesis/frontend/client/package-lock.json +++ b/nesis/frontend/client/package-lock.json @@ -8,6 +8,8 @@ "name": "ametnes-nesis", "version": "0.1.0", "dependencies": { + "@azure/msal-browser": "^3.14.0", + "@azure/msal-react": "^2.0.16", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.3", @@ -24,7 +26,7 @@ "query-string": "^8.1.0", "react": "^18.2.0", "react-bootstrap": "^2.9.0", - "react-bootstrap-icons": "^1.0.1-alpha3", + "react-bootstrap-icons": "^1.11.4", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-icons": "^4.11.0", @@ -105,6 +107,37 @@ "x-default-browser": "bin/x-default-browser.js" } }, + "node_modules/@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "dependencies": { + "@azure/msal-common": "14.10.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-react": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@azure/msal-browser": "^3.14.0", + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/@babel/cli": { "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", @@ -4125,6 +4158,12 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "peer": true + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -11215,6 +11254,18 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "peer": true, + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -21836,9 +21887,9 @@ } }, "node_modules/react-bootstrap-icons": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-f/DAy4UXnjdbaZyUcZKR2I3xim56uCznb9t+u3ojwzDG1p2RUrua/d8R4xplAQ8Bj/LVZwHVSrvO+npvp3l3pw==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.4.tgz", + "integrity": "sha512-lnkOpNEZ/Zr7mNxvjA9efuarCPSgtOuGA55XiRj7ASJnBjb1wEAdtJOd2Aiv9t07r7FLI1IgyZPg9P6jqWD/IA==", "dependencies": { "prop-types": "^15.7.2" }, @@ -24923,6 +24974,19 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -25676,7 +25740,7 @@ "version": "2.25.4", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-html-community": "0.0.8", "html-entities": "^2.1.0", @@ -26465,6 +26529,25 @@ "default-browser-id": "3.0.0" } }, + "@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "requires": { + "@azure/msal-common": "14.10.0" + } + }, + "@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==" + }, + "@azure/msal-react": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", + "requires": {} + }, "@babel/cli": { "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", @@ -26931,7 +27014,8 @@ "@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==" + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "requires": {} }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -28064,12 +28148,14 @@ "@csstools/postcss-unset-value": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==" + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "requires": {} }, "@csstools/selector-specificity": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==" + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "requires": {} }, "@discoveryjs/json-ext": { "version": "0.5.7", @@ -28207,7 +28293,8 @@ "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==" + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "requires": {} }, "@emotion/utils": { "version": "1.2.1", @@ -29108,6 +29195,12 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "peer": true + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -29215,7 +29308,8 @@ "@mui/types": { "version": "7.2.12", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.12.tgz", - "integrity": "sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw==" + "integrity": "sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw==", + "requires": {} }, "@mui/utils": { "version": "5.15.3", @@ -29712,7 +29806,8 @@ "uncontrollable": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", - "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==" + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "requires": {} } } }, @@ -31116,7 +31211,8 @@ "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -31444,7 +31540,8 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.4.6.tgz", "integrity": "sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw==", - "dev": true + "dev": true, + "requires": {} }, "@storybook/react-webpack5": { "version": "7.4.6", @@ -31580,7 +31677,8 @@ "version": "14.5.1", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -33098,12 +33196,14 @@ "acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==" + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -33182,7 +33282,8 @@ "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} }, "ansi-escapes": { "version": "4.3.2", @@ -33481,7 +33582,8 @@ "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true + "dev": true, + "requires": {} }, "babel-jest": { "version": "27.5.1", @@ -33608,7 +33710,8 @@ "babel-plugin-named-asset-import": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "requires": {} }, "babel-plugin-named-exports-order": { "version": "0.0.2", @@ -33863,7 +33966,8 @@ "bootstrap": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==" + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "requires": {} }, "bplist-parser": { "version": "0.2.0", @@ -34068,6 +34172,15 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "chart.js": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "peer": true, + "requires": { + "@kurkle/color": "^0.3.0" + } + }, "check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -34501,7 +34614,8 @@ "css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==" + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "requires": {} }, "css-has-pseudo": { "version": "3.0.4", @@ -34584,7 +34698,8 @@ "css-prefers-color-scheme": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "requires": {} }, "css-select": { "version": "4.3.0", @@ -34699,7 +34814,8 @@ "cssnano-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "requires": {} }, "csso": { "version": "4.2.0", @@ -35819,7 +35935,8 @@ "eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==" + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "requires": {} }, "eslint-plugin-storybook": { "version": "0.6.14", @@ -36968,7 +37085,8 @@ "highcharts-react-official": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.1.tgz", - "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==" + "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==", + "requires": {} }, "history": { "version": "4.10.1", @@ -37194,7 +37312,8 @@ "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "requires": {} }, "idb": { "version": "7.1.1", @@ -38416,7 +38535,8 @@ "jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==" + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "requires": {} }, "jest-regex-util": { "version": "27.5.1", @@ -39710,7 +39830,8 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz", "integrity": "sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==", - "dev": true + "dev": true, + "requires": {} }, "mdast-util-definitions": { "version": "4.0.0", @@ -40674,7 +40795,8 @@ "postcss-browser-comments": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "requires": {} }, "postcss-calc": { "version": "8.2.4", @@ -40772,22 +40894,26 @@ "postcss-discard-comments": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==" + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "requires": {} }, "postcss-double-position-gradients": { "version": "3.1.2", @@ -40809,7 +40935,8 @@ "postcss-flexbugs-fixes": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "requires": {} }, "postcss-focus-visible": { "version": "6.0.4", @@ -40830,12 +40957,14 @@ "postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "requires": {} }, "postcss-gap-properties": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==" + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "requires": {} }, "postcss-image-set-function": { "version": "4.0.7", @@ -40858,7 +40987,8 @@ "postcss-initial": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "requires": {} }, "postcss-js": { "version": "4.0.1", @@ -40906,12 +41036,14 @@ "postcss-logical": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "requires": {} }, "postcss-merge-longhand": { "version": "5.1.7", @@ -40972,7 +41104,8 @@ "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.3", @@ -41030,7 +41163,8 @@ "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -41101,7 +41235,8 @@ "postcss-opacity-percentage": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", - "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==" + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "requires": {} }, "postcss-ordered-values": { "version": "5.1.3", @@ -41123,7 +41258,8 @@ "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "requires": {} }, "postcss-place": { "version": "7.0.5", @@ -41217,7 +41353,8 @@ "postcss-replace-overflow-wrap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "requires": {} }, "postcss-selector-not": { "version": "6.0.1", @@ -41671,9 +41808,9 @@ } }, "react-bootstrap-icons": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-f/DAy4UXnjdbaZyUcZKR2I3xim56uCznb9t+u3ojwzDG1p2RUrua/d8R4xplAQ8Bj/LVZwHVSrvO+npvp3l3pw==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.4.tgz", + "integrity": "sha512-lnkOpNEZ/Zr7mNxvjA9efuarCPSgtOuGA55XiRj7ASJnBjb1wEAdtJOd2Aiv9t07r7FLI1IgyZPg9P6jqWD/IA==", "requires": { "prop-types": "^15.7.2" } @@ -41681,13 +41818,15 @@ "react-chartjs-2": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==" + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "requires": {} }, "react-colorful": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", - "dev": true + "dev": true, + "requires": {} }, "react-confetti": { "version": "6.1.0", @@ -41814,7 +41953,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} }, "react-dom": { "version": "18.2.0", @@ -41857,13 +41997,15 @@ "react-icons": { "version": "4.11.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==" + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "requires": {} }, "react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", - "dev": true + "dev": true, + "requires": {} }, "react-is": { "version": "17.0.2", @@ -43251,7 +43393,8 @@ "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==" + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "requires": {} }, "styled-components": { "version": "6.0.8", @@ -43478,7 +43621,8 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz", "integrity": "sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==", - "dev": true + "dev": true, + "requires": {} }, "symbol-tree": { "version": "3.2.4", @@ -43989,6 +44133,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -44185,7 +44335,8 @@ "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "requires": {} }, "use-resize-observer": { "version": "9.1.0", @@ -44526,7 +44677,8 @@ "ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==" + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "requires": {} } } }, @@ -44534,7 +44686,7 @@ "version": "2.25.4", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", - "dev": true, + "devOptional": true, "requires": { "ansi-html-community": "0.0.8", "html-entities": "^2.1.0", @@ -45062,7 +45214,8 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/nesis/frontend/client/package.json b/nesis/frontend/client/package.json index 9916d0b..8741498 100644 --- a/nesis/frontend/client/package.json +++ b/nesis/frontend/client/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@azure/msal-browser": "^3.14.0", + "@azure/msal-react": "^2.0.16", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.3", @@ -19,6 +21,7 @@ "query-string": "^8.1.0", "react": "^18.2.0", "react-bootstrap": "^2.9.0", + "react-bootstrap-icons": "^1.11.4", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-icons": "^4.11.0", @@ -27,7 +30,6 @@ "react-scripts": "5.0.1", "react-select": "^5.7.7", "styled-components": "^6.0.8", - "react-bootstrap-icons": "^1.0.1-alpha3", "superagent": "^8.1.2", "uuid": "^9.0.1", "web-vitals": "^2.1.4" diff --git a/nesis/frontend/client/src/ConfigContext.js b/nesis/frontend/client/src/ConfigContext.js new file mode 100644 index 0000000..b469142 --- /dev/null +++ b/nesis/frontend/client/src/ConfigContext.js @@ -0,0 +1,31 @@ +import React, { useContext, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import useClient from './utils/useClient'; + +const ConfigContext = React.createContext({}); + +ConfigContextProvider.propTypes = { + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + ]).isRequired, +}; + +export default ConfigContext; + +export function ConfigContextProvider({ children }) { + const client = useClient(); + const [config, setConfig] = useState(null); + useEffect(() => { + client.get('config').then((res) => { + setConfig(JSON.parse(res.text)); + }); + }, [setConfig, client]); + return ( + {children} + ); +} + +export function useConfig() { + return useContext(ConfigContext); +} diff --git a/nesis/frontend/client/src/SessionContext.js b/nesis/frontend/client/src/SessionContext.js index 6c50f6f..87dacea 100644 --- a/nesis/frontend/client/src/SessionContext.js +++ b/nesis/frontend/client/src/SessionContext.js @@ -10,6 +10,8 @@ import { clearToken } from './utils/tokenStorage'; import { useHistory } from 'react-router-dom'; import useSessionStorage from './utils/useSessionStorage'; import apiClient from './utils/httpClient'; +import { PublicClientApplication } from '@azure/msal-browser'; + const SessionContext = React.createContext({ session: null, setSession: () => '', @@ -24,7 +26,7 @@ SessionProvider.propTypes = { export default SessionContext; -const SESSION_KEY = 'AMETNES_CURRENT_SESSION'; +const SESSION_KEY = 'NESIS_CURRENT_SESSION'; export function SessionProvider({ children }) { const [session, setSession] = useSessionStorage(SESSION_KEY); @@ -55,32 +57,40 @@ export function useCurrentUser() { return context && context.user; } -export function useSignOut(client) { +export function useSignOut(client, config) { const context = useContext(SessionContext); const setSession = context?.setSession; const history = useHistory(); return useCallback( function () { - logoutFromGoogle(); - logoutFromOptimAI(client); + logoutNesis(client); clearToken(); setSession(null); + logoutMicrosoft(config); history.push('/signin'); }, [setSession, history], ); } -function logoutFromGoogle() { - if (window.gapi && window.gapi.auth2) { - const auth2 = window.gapi.auth2.getAuthInstance(); - if (auth2 != null) { - auth2.signOut().then(auth2.disconnect()); - } +async function logoutMicrosoft(config) { + try { + const msalInstance = new PublicClientApplication({ + auth: { + clientId: config?.auth?.OAUTH_AZURE_CLIENT_ID, + authority: config?.auth?.OAUTH_AZURE_AUTHORITY, + redirectUri: config?.auth?.OAUTH_AZURE_REDIRECTURI, + postLogoutRedirectUri: 'http://localhost:3000/', + }, + }); + await msalInstance.initialize(); + msalInstance.logoutRedirect(); + } catch (e) { + /* ignored */ } } -function logoutFromOptimAI(client) { +function logoutNesis(client) { if (!client) { return; } diff --git a/nesis/frontend/client/src/components/AzureButton.js b/nesis/frontend/client/src/components/AzureButton.js new file mode 100644 index 0000000..8f23dc0 --- /dev/null +++ b/nesis/frontend/client/src/components/AzureButton.js @@ -0,0 +1,70 @@ +import React, { useContext, useEffect, useState } from 'react'; +import useClient from '../utils/useClient'; +import { useConfig } from '../ConfigContext'; +import parseApiErrorMessage from '../utils/parseApiErrorMessage'; +import { PublicClientApplication } from '@azure/msal-browser'; +import MicrosoftIcon from '../images/MicrosoftIcon.png'; +import classes from '../styles/SignInPage.module.css'; + +export default function AzureButton({ onFailure, onSuccess }) { + const config = useConfig(); + const client = useClient(); + const msalInstance = new PublicClientApplication({ + auth: { + clientId: config?.auth?.OAUTH_AZURE_CLIENT_ID, + authority: config?.auth?.OAUTH_AZURE_AUTHORITY, + redirectUri: config?.auth?.OAUTH_AZURE_REDIRECTURI, + navigateToLoginRequestUrl: true, + }, + cache: { + cacheLocation: config?.auth?.OAUTH_AZURE_CACHELOCATION, + storeAuthStateInCookie: config?.auth?.OAUTH_AZURE_STOREAUTHSTATEINCOOKIE, + }, + }); + + useEffect(() => { + const initialize = async () => { + await msalInstance.initialize(); + await msalInstance + .handleRedirectPromise() + .then((response) => { + if (response?.accessToken) { + client + .post('sessions', { + azure: response, + }) + .then((response) => { + onSuccess(response?.body?.email, response); + }) + .catch((error) => { + onFailure(parseApiErrorMessage(error)); + }); + } + }) + .catch((error) => { + onFailure(parseApiErrorMessage(error)); + }); + }; + try { + initialize(); + } catch (e) { + onFailure('Error log in in with Azure'); + } + }, []); + + const handleLogin = async (evt) => { + evt.preventDefault(); + + await msalInstance.initialize(); + await msalInstance.loginRedirect(); + }; + + return ( + <> + + + ); +} diff --git a/nesis/frontend/client/src/components/AzureButton.test.js b/nesis/frontend/client/src/components/AzureButton.test.js new file mode 100644 index 0000000..3a1ff93 --- /dev/null +++ b/nesis/frontend/client/src/components/AzureButton.test.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { renderWithContext, renderWithRouter } from '../utils/testUtils'; +import AzureButton from './AzureButton'; + +describe('', () => { + it('render azure authenticate option on login and register screen', async () => { + const { getByText } = renderWithContext(); + const buttonComponent = getByText('Continue With Microsoft'); + expect(buttonComponent).toBeInTheDocument(); + }); +}); diff --git a/nesis/frontend/client/src/components/Menu.js b/nesis/frontend/client/src/components/Menu.js index fd2e611..3544ceb 100644 --- a/nesis/frontend/client/src/components/Menu.js +++ b/nesis/frontend/client/src/components/Menu.js @@ -3,8 +3,10 @@ import styled from 'styled-components/macro'; import { NavLink } from 'react-router-dom'; import { device } from '../utils/breakpoints'; import { useSignOut } from '../SessionContext'; +import { useConfig } from '../ConfigContext'; import MenuHeader from './MenuHeader'; import { GearFill, FileEarmarkPost, ArrowBarLeft } from 'react-bootstrap-icons'; +import client from '../utils/httpClient'; const sideMenuWidth = 300; @@ -56,7 +58,7 @@ const MainContainer = styled.div` flex-direction: column; `; -function Optim({ children, className }) { +function Nesis({ children, className }) { const [mobileModalVisible, setModalVisible] = useState(false); const background = 'white'; @@ -133,7 +135,8 @@ const MenuTitle = styled.div` `; function SideMenu({ onClose }) { - const signOut = useSignOut(); + const config = useConfig(); + const signOut = useSignOut(client, config); function closeMenu() { if (onClose) { @@ -170,4 +173,4 @@ function SideMenu({ onClose }) { ); } -export default Optim; +export default Nesis; diff --git a/nesis/frontend/client/src/components/MenuHeader.js b/nesis/frontend/client/src/components/MenuHeader.js index 51944db..dc498bf 100644 --- a/nesis/frontend/client/src/components/MenuHeader.js +++ b/nesis/frontend/client/src/components/MenuHeader.js @@ -7,6 +7,7 @@ import { ReactComponent as Logo } from '../images/Nesis.svg'; import styled from 'styled-components/macro'; import { device } from '../utils/breakpoints'; import client from '../utils/httpClient'; +import { useConfig } from '../ConfigContext'; import { LightSquareButton } from './inputs/SquareButton'; const Main = styled.div` @@ -78,7 +79,8 @@ const HeaderToolBarIcon = styled.div` export default function MenuHeader({ onMobileMenuClick }) { const history = useHistory(); - const signOut = useSignOut(client); + const config = useConfig(); + const signOut = useSignOut(client, config); const session = useCurrentSession(); const iconSize = 20; @@ -94,10 +96,7 @@ export default function MenuHeader({ onMobileMenuClick }) { {session && ( - + Help diff --git a/nesis/frontend/client/src/images/MicrosoftIcon.png b/nesis/frontend/client/src/images/MicrosoftIcon.png new file mode 100644 index 0000000..9e5e2bc Binary files /dev/null and b/nesis/frontend/client/src/images/MicrosoftIcon.png differ diff --git a/nesis/frontend/client/src/images/MicrosoftIcon.svg b/nesis/frontend/client/src/images/MicrosoftIcon.svg new file mode 100644 index 0000000..1f73976 --- /dev/null +++ b/nesis/frontend/client/src/images/MicrosoftIcon.svg @@ -0,0 +1 @@ +MS-SymbolLockup \ No newline at end of file diff --git a/nesis/frontend/client/src/index.js b/nesis/frontend/client/src/index.js index c576590..9eb5261 100644 --- a/nesis/frontend/client/src/index.js +++ b/nesis/frontend/client/src/index.js @@ -7,13 +7,16 @@ import { ThemeProvider } from 'styled-components/macro'; import theme from './utils/theme'; import { SessionProvider } from './SessionContext'; import { ToasterContextProvider } from './ToasterContext'; +import { ConfigContextProvider } from './ConfigContext'; ReactDOM.render( - + + + diff --git a/nesis/frontend/client/src/pages/DocumentGPT/ChatPage.js b/nesis/frontend/client/src/pages/DocumentGPT/ChatPage.js index ff4ce69..92b46c5 100644 --- a/nesis/frontend/client/src/pages/DocumentGPT/ChatPage.js +++ b/nesis/frontend/client/src/pages/DocumentGPT/ChatPage.js @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { useHistory, Route, Switch, useRouteMatch } from 'react-router-dom'; -import { GearFill } from 'react-bootstrap-icons'; -import Optim from '../../components/Menu'; +import Nesis from '../../components/Menu'; import styled from 'styled-components/macro'; import { FileEarmarkPost } from 'react-bootstrap-icons'; import { @@ -140,7 +139,7 @@ const ChatPage = () => { }; return ( - + Chat with your Documents @@ -220,7 +219,7 @@ const ChatPage = () => { - + ); }; diff --git a/nesis/frontend/client/src/pages/Settings/SettingPage.js b/nesis/frontend/client/src/pages/Settings/SettingPage.js index 176969f..bc81148 100644 --- a/nesis/frontend/client/src/pages/Settings/SettingPage.js +++ b/nesis/frontend/client/src/pages/Settings/SettingPage.js @@ -5,7 +5,7 @@ import { GearFill } from 'react-bootstrap-icons'; import DatasourcesPage from './Datasources/DatasourcesPage'; import UsersPage from './Users/UsersPage'; import RolesPage from './Roles/RolesPage'; -import Optim from '../../components/Menu'; +import Nesis from '../../components/Menu'; import styled from 'styled-components/macro'; const Heading = styled.h1` @@ -23,7 +23,7 @@ const SettingsPage = () => { const history = useHistory(); const match = useRouteMatch(); return ( - + Settings @@ -68,7 +68,7 @@ const SettingsPage = () => { - + ); }; diff --git a/nesis/frontend/client/src/pages/SignInPage.js b/nesis/frontend/client/src/pages/SignInPage.js index c5ed91d..8643ba8 100644 --- a/nesis/frontend/client/src/pages/SignInPage.js +++ b/nesis/frontend/client/src/pages/SignInPage.js @@ -9,9 +9,14 @@ import FullPageFormContainer from '../components/layout/FullPageFormContainer'; import client from '../utils/httpClient'; import parseApiErrorMessage from '../utils/parseApiErrorMessage'; import SessionContext from '../SessionContext'; +import { useConfig } from '../ConfigContext'; import { useHistory } from 'react-router-dom'; import { setToken } from '../utils/tokenStorage'; import MessageRow from '../components/MessageRow'; +import { Col, Container, Row, Form } from 'react-bootstrap'; +import classes from '../styles/SignInPage.module.css'; +import AzureButton from '../components/AzureButton'; +import { Toggles } from 'react-bootstrap-icons'; const LogoContainer = styled.div` margin-top: 32px; @@ -84,8 +89,12 @@ const HTTP_STATUS_UNAUTHORIZED = 401; const SignInPage = () => { const [error, setError] = useState(); + const [toggleCreds, setToggleCreds] = useState(false); const { setSession } = useContext(SessionContext); const history = useHistory(); + const config = useConfig(); + const azureAuthEnabled = config?.auth?.OAUTH_AZURE_ENABLED; + const oauthEnabled = azureAuthEnabled; function submit(session, actions) { client @@ -124,42 +133,70 @@ const SignInPage = () => { Nesis Your Enterprise Knowledge Partner {error} - - {({ isSubmitting, resetForm }) => ( - - - - - - - - - - Log In - - - - - )} - + + + + + {azureAuthEnabled && !toggleCreds && ( + + )} + + + + {(!oauthEnabled || toggleCreds) && ( +
+ + {({ isSubmitting, resetForm }) => ( + + + + + + + + + + Log In + + + + + )} + +
+ )} + {oauthEnabled && ( +
+ setToggleCreds(!toggleCreds)} + > + Use {!toggleCreds ? 'password' : 'Azure'} + +
+ )} diff --git a/nesis/frontend/client/src/styles/SignInPage.module.css b/nesis/frontend/client/src/styles/SignInPage.module.css new file mode 100644 index 0000000..5827a8b --- /dev/null +++ b/nesis/frontend/client/src/styles/SignInPage.module.css @@ -0,0 +1,32 @@ +.colsign { + margin: 0 auto; +} + +@media only screen and (max-width: 450px) { + .w25 { + width: 160px !important; + } +} + +@media only screen and (max-width: 992px) { + .formsize { + width: 100%; + padding: 30px !important; + } + .signinheading { + text-align: center; + } + .orloginbutton { + margin: 0 !important; + } +} + +.orloginbutton { + border: 1px solid rgb(0, 18, 30); + padding: 15px 3px; + border-radius: 1px; + background-color: white; + color: black; + width: 100%; + font-size: 12px; +} diff --git a/nesis/frontend/package-lock.json b/nesis/frontend/package-lock.json index ef89a69..876be85 100644 --- a/nesis/frontend/package-lock.json +++ b/nesis/frontend/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "license": "ISC", "dependencies": { + "@azure/msal-browser": "^3.14.0", + "@azure/msal-react": "^2.0.16", "app-root-path": "^3.1.0", "body-parser": "^1.20.2", "cors": "^2.8.5", @@ -39,6 +41,37 @@ "node": ">=6.0.0" } }, + "node_modules/@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "dependencies": { + "@azure/msal-common": "14.10.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-react": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@azure/msal-browser": "^3.14.0", + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -2098,8 +2131,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -2336,6 +2368,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -3255,6 +3299,18 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -4077,6 +4133,25 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "requires": { + "@azure/msal-common": "14.10.0" + } + }, + "@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==" + }, + "@azure/msal-react": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", + "requires": {} + }, "@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -5640,8 +5715,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.1", @@ -5841,6 +5915,15 @@ } } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -6544,6 +6627,15 @@ "unpipe": "1.0.0" } }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, "readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", diff --git a/nesis/frontend/package.json b/nesis/frontend/package.json index fb2b4d3..708c544 100644 --- a/nesis/frontend/package.json +++ b/nesis/frontend/package.json @@ -25,6 +25,8 @@ "url": "https://github.com/ametnes/nesis/issues" }, "dependencies": { + "@azure/msal-browser": "^3.14.0", + "@azure/msal-react": "^2.0.16", "app-root-path": "^3.1.0", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/nesis/frontend/server/api/config.js b/nesis/frontend/server/api/config.js new file mode 100644 index 0000000..2a2e3e9 --- /dev/null +++ b/nesis/frontend/server/api/config.js @@ -0,0 +1,20 @@ +const getConfig = (requests, profile) => (request, response) => { + const config = { + version: 'v1', + auth: { + OAUTH_AZURE_ENABLED: profile.NESIS_OAUTH_AZURE_ENABLED, + OAUTH_AZURE_CLIENT_ID: profile.NESIS_OAUTH_AZURE_CLIENT_ID, + OAUTH_AZURE_AUTHORITY: profile.NESIS_OAUTH_AZURE_AUTHORITY, + OAUTH_AZURE_REDIRECTURI: profile.NESIS_OAUTH_AZURE_REDIRECTURI, + OAUTH_AZURE_CACHELOCATION: profile.NESIS_OAUTH_AZURE_CACHELOCATION, + OAUTH_AZURE_STOREAUTHSTATEINCOOKIE: + profile.NESIS_OAUTH_AZURE_STOREAUTHSTATEINCOOKIE, + OAUTH_AZURE_SCOPES: profile.NESIS_OAUTH_AZURE_SCOPES || ['User.Read'], + }, + }; + response.status(200).send(JSON.stringify(config)); +}; + +module.exports = { + get: getConfig, +}; diff --git a/nesis/frontend/server/api/datasources.spec.js b/nesis/frontend/server/api/datasources.spec.js index 9ef5a49..baef92b 100644 --- a/nesis/frontend/server/api/datasources.spec.js +++ b/nesis/frontend/server/api/datasources.spec.js @@ -1,5 +1,5 @@ const api = require('./datasources'); -const config = require('../config'); +const config = require('../profile'); const sinon = require('sinon'); const { Request } = require('../util/test-util'); diff --git a/nesis/frontend/server/api/index.js b/nesis/frontend/server/api/index.js index e69eb63..de48da4 100644 --- a/nesis/frontend/server/api/index.js +++ b/nesis/frontend/server/api/index.js @@ -5,3 +5,4 @@ module.exports.settings = require('./settings'); module.exports.roles = require('./roles'); module.exports.users = require('./users'); module.exports.tasks = require('./tasks'); +module.exports.config = require('./config'); diff --git a/nesis/frontend/server/api/sessions.js b/nesis/frontend/server/api/sessions.js index 9667c49..b437b01 100644 --- a/nesis/frontend/server/api/sessions.js +++ b/nesis/frontend/server/api/sessions.js @@ -3,25 +3,39 @@ const logger = require('../util/logger'); const post = (requests, profile) => async (request, response) => { const url = profile.SERVICE_ENDPOINT; const session = request.body; + const oauth_token_key = profile.NESIS_OAUTH_TOKEN_KEY; - if (!session) { + // session cannot contain the oauth token key + if (!session || oauth_token_key in session) { return response.status(400).send({ message: 'Invalid request. session not supplied', }); } - logger.info(`Posting to ${url}/sessions`); - requests - .post(`${url}/sessions`) - .set('Accept', 'application/json') - .set('Content-Type', 'application/json') - .send(session) + let oauthProvider = null; + + if (session.azure) { + oauthProvider = authenticateWithAzure(requests, profile, session.azure); + } else { + oauthProvider = requests + .post(`${url}/sessions`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/json') + .send({ ...session }); + } + + oauthProvider .then((res) => { response.status(res.status).send(res.body); }) .catch((err) => { - logger.error(`Fail posting to ${url}: ${err}, status: ${err.status}`); - response.status(err.status).send(err.response.body); + if (err && err.response && err.response.body) { + response.status(err.status).send(err.response.body); + } else if (err) { + response.status(err.status || 400).send('Error processing request'); + } else { + response.status(500).send('Error processing request'); + } }); }; @@ -39,13 +53,65 @@ const _delete = (requests, url) => (request, response) => { }) .catch((err) => { logger.error( - `Fail posting to ${url}: ${JSON.stringify( - err.response.body, - )} with status ${err.status}`, + `Fail posting to ${url}: ${JSON.stringify(err)} with status ${ + err.status + }`, ); response.status(err.status).send(err.response.body); }); }; +function authenticateWithAzure(requests, profile, azure) { + const email = azure.account.username; + const name = azure.account.name; + + const graphUrl = 'https://graph.microsoft.com/v1.0/me'; + + return requests + .get(graphUrl) + .set('Authorization', `Bearer ${azure.accessToken}`) + .set('Content-Type', 'application/json') + .send() + .then((res) => { + if (res.body.mail !== email) { + const messsage = 'Invalid azure access token'; + const error = new Error(messsage); + Object.assign(error, { + status: 401, + response: { + body: 'Invalid azure access token', + }, + }); + throw error; + } + }) + .then(() => sendOauthSession(requests, name, email, profile)); +} + +function sendOauthSession(requests, name, email, profile) { + const url = profile.SERVICE_ENDPOINT; + const oauth_token_key = profile.NESIS_OAUTH_TOKEN_KEY; + const oauth_token_value = profile.NESIS_OAUTH_TOKEN_VALUE; + const payload = { + email: email, + name: name, + [oauth_token_key]: oauth_token_value, + }; + + return requests + .post(`${url}/sessions`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/json') + .send(payload) + .then((res) => ({ + status: res.status, + body: { + ...res.body, + email, + name, + }, + })); +} + module.exports.post = post; module.exports.delete = _delete; diff --git a/nesis/frontend/server/api/sessions.spec.js b/nesis/frontend/server/api/sessions.spec.js index f2109f6..80df11d 100644 --- a/nesis/frontend/server/api/sessions.spec.js +++ b/nesis/frontend/server/api/sessions.spec.js @@ -1,6 +1,7 @@ const api = require('./sessions'); -const config = require('../config'); +const config = require('../profile'); const sinon = require('sinon'); +const assert = require('chai').assert; const { Request } = require('../util/test-util'); describe('Sessions', () => { @@ -32,4 +33,88 @@ describe('Sessions', () => { ); sinon.assert.match('POST', requests.method); }); + + it('can not be created with token key in payload', () => { + const requests = new Request(); + const request = { + headers: {}, + body: { + email: 'email@domain.com', + [config.profile.DEV.NESIS_OAUTH_TOKEN_KEY]: + 'some.one.knows.something.about.our.internals', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + const post = api.post(requests, config.profile.DEV); + post(request, response); + sinon.assert.calledWith(statusStab, 400); + sinon.assert.called(responseStab); + }); + + it('can not be created with no payload', () => { + const requests = new Request(); + const request = { + headers: {}, + body: null, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + const post = api.post(requests, config.profile.DEV); + post(request, response); + sinon.assert.calledWith(statusStab, 400); + sinon.assert.called(responseStab); + }); + + it('can be created with Azure', () => { + const requests = new Request(); + requests.azureUserData = { + displayName: 'Michy Tan', + mail: 'michy.tan@acme.onmicrosoft.com', + userPrincipalName: 'michy.tan@acme.onmicrosoft.com', + }; + const request = { + headers: {}, + body: { + email: 'michy.tan@acme.onmicrosoft.com', + azure: { + authority: 'https://login.microsoftonline.com/common/', + uniqueId: '9cdd0b94-0000-48db-8bc8-00000000', + tenantId: '041e34c2-0000-44b0-8aa4-00000000', + accessToken: 'some.key', + account: { + username: 'michy.tan@acme.onmicrosoft.com', + name: 'First Last', + }, + }, + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + const post = api.post(requests, config.profile.DEV); + post(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + + assert.deepEqual(requests.data, { + email: 'michy.tan@acme.onmicrosoft.com', + name: 'First Last', + ___nesis_oauth_token_key___: '___nesis_oauth_token_value___', + }); + }); }); diff --git a/nesis/frontend/server/api/tasks.spec.js b/nesis/frontend/server/api/tasks.spec.js index 6771aae..d846e7f 100644 --- a/nesis/frontend/server/api/tasks.spec.js +++ b/nesis/frontend/server/api/tasks.spec.js @@ -1,5 +1,5 @@ const api = require('./tasks'); -const config = require('../config'); +const config = require('../profile'); const sinon = require('sinon'); const { Request } = require('../util/test-util'); diff --git a/nesis/frontend/server/config.js b/nesis/frontend/server/config.js deleted file mode 100644 index 77520ee..0000000 --- a/nesis/frontend/server/config.js +++ /dev/null @@ -1,13 +0,0 @@ -const profile = { - PROD: { - SERVICE_ENDPOINT: `${process.env.API_URL}/v1`, - }, - TEST: { - SERVICE_ENDPOINT: `${process.env.API_URL}/v1`, - }, - DEV: { - SERVICE_ENDPOINT: `http://localhost:6000/v1`, - }, -}; - -module.exports.profile = profile; diff --git a/nesis/frontend/server/main.js b/nesis/frontend/server/main.js index 6cd172c..ccbfb43 100644 --- a/nesis/frontend/server/main.js +++ b/nesis/frontend/server/main.js @@ -7,7 +7,7 @@ const logger = require('./util/logger'); const api = require('./api/index'); const app = express(); -const config = require('./config'); +const config = require('./profile'); const profile = config.profile[process.env.PROFILE] || config.profile.DEV; //Handles post requests const bodyParser = require('body-parser'); @@ -29,7 +29,7 @@ async function init() { USERS: '/api/users', TASKS: '/api/tasks', DATA_SOURCES: '/api/datasources', - DATA_SOURCES_MODELS: '/api/models', + CONFIG: '/api/config', QANDA_PREDICTIONS: '/api/qanda/predictions', }; @@ -90,6 +90,7 @@ async function init() { `${API.QANDA_PREDICTIONS}/:id`, api.qanda_predictions.getById(requests, profile), ); + app.get(`${API.CONFIG}`, api.config.get(requests, profile)); app.get('/*', (req, res) => { res.sendFile(path.join(home, 'index.html')); diff --git a/nesis/frontend/server/profile.js b/nesis/frontend/server/profile.js new file mode 100644 index 0000000..7d267be --- /dev/null +++ b/nesis/frontend/server/profile.js @@ -0,0 +1,38 @@ +const profile = { + PROD: { + SERVICE_ENDPOINT: `${process.env.API_URL}/v1`, + NESIS_OAUTH_TOKEN_KEY: process.env.NESIS_OAUTH_TOKEN_KEY, + NESIS_OAUTH_TOKEN_VALUE: process.env.NESIS_OAUTH_TOKEN_VALUE, + NESIS_OAUTH_AZURE_ENABLED: process.env.NESIS_OAUTH_AZURE_ENABLED, + NESIS_OAUTH_AZURE_CLIENT_ID: process.env.NESIS_OAUTH_AZURE_CLIENT_ID, + NESIS_OAUTH_AZURE_TENANT_ID: process.env.NESIS_OAUTH_AZURE_TENANT_ID, + NESIS_OAUTH_AZURE_AUTHORITY: 'https://login.microsoftonline.com/common/', + NESIS_OAUTH_AZURE_REDIRECTURI: process.env.NESIS_OAUTH_AZURE_REDIRECTURI, + NESIS_OAUTH_AZURE_CACHELOCATION: + process.env.NESIS_OAUTH_AZURE_CACHELOCATION || 'sessionStorage', + NESIS_OAUTH_AZURE_STOREAUTHSTATEINCOOKIE: + process.env.NESIS_OAUTH_AZURE_STOREAUTHSTATEINCOOKIE || false, + NESIS_OAUTH_AZURE_SCOPES: process.env.NESIS_OAUTH_AZURE_SCOPES || [ + 'User.Read', + ], + }, + DEV: { + SERVICE_ENDPOINT: `http://localhost:6000/v1`, + NESIS_OAUTH_TOKEN_KEY: '___nesis_oauth_token_key___', + NESIS_OAUTH_TOKEN_VALUE: '___nesis_oauth_token_value___', + NESIS_OAUTH_AZURE_ENABLED: true, + NESIS_OAUTH_AZURE_CLIENT_ID: process.env.NESIS_OAUTH_AZURE_CLIENT_ID, + NESIS_OAUTH_AZURE_TENANT_ID: process.env.NESIS_OAUTH_AZURE_TENANT_ID, + NESIS_OAUTH_AZURE_AUTHORITY: 'https://login.microsoftonline.com/common/', + NESIS_OAUTH_AZURE_REDIRECTURI: 'http://localhost:3000/', + NESIS_OAUTH_AZURE_CACHELOCATION: + process.env.NESIS_OAUTH_AZURE_CACHELOCATION || 'localStorage', + NESIS_OAUTH_AZURE_STOREAUTHSTATEINCOOKIE: + process.env.NESIS_OAUTH_AZURE_STOREAUTHSTATEINCOOKIE || false, + NESIS_OAUTH_AZURE_SCOPES: process.env.NESIS_OAUTH_AZURE_SCOPES || [ + 'User.Read', + ], + }, +}; + +module.exports.profile = profile; diff --git a/nesis/frontend/server/util/test-util.js b/nesis/frontend/server/util/test-util.js index 571f934..8c45eeb 100644 --- a/nesis/frontend/server/util/test-util.js +++ b/nesis/frontend/server/util/test-util.js @@ -20,6 +20,7 @@ class Request { this.tasks = []; this.taskById = {}; this.targets = {}; + this.azureUserData = {}; } set(header, value) { this.headers[header] = value; @@ -114,6 +115,8 @@ class Request { res = { body: this.tasks, status: 200 }; } else if (this.url.includes('/tasks/')) { res = { body: this.taskById, status: 200 }; + } else if (this.url.endsWith('/v1.0/me')) { + res = { body: this.azureUserData, status: 200 }; } } else if (this.method === 'DELETE') { res = { body: 'Deleted', status: 200 };