diff --git a/src/v2/.devcontainer/Dockerfile b/src/v2/.devcontainer/Dockerfile index a317e41d..6c703dbd 100644 --- a/src/v2/.devcontainer/Dockerfile +++ b/src/v2/.devcontainer/Dockerfile @@ -3,8 +3,8 @@ FROM almalinux:latest RUN dnf -y install \ openssh-clients \ nodejs \ - openldap-devel \ - rm -rf /var/cache/yum && \z + openldap-devel && \ + rm -rf /var/cache/yum && \ npm i -g n && \ n 16 diff --git a/src/v2/client/.env b/src/v2/client/.env new file mode 100644 index 00000000..94a970fb --- /dev/null +++ b/src/v2/client/.env @@ -0,0 +1,3 @@ +BROWSER=none +REACT_APP_AUTH_URL=https://engineering.snow.edu/aspen/auth +REACT_APP_BASE_URL=https://localhost:44478/aspen/new \ No newline at end of file diff --git a/src/v2/client/package-lock.json b/src/v2/client/package-lock.json index 8fb3396d..9dd5ecb2 100644 --- a/src/v2/client/package-lock.json +++ b/src/v2/client/package-lock.json @@ -22,6 +22,7 @@ "bootstrap": "^5.3.1", "bootstrap-icons": "^1.11.1", "eslint": "^8.50.0", + "oidc-client": "^1.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", @@ -5440,6 +5441,25 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6154,6 +6174,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -12662,6 +12687,37 @@ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, + "node_modules/oidc-client": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.11.5.tgz", + "integrity": "sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==", + "dependencies": { + "acorn": "^7.4.1", + "base64-js": "^1.5.1", + "core-js": "^3.8.3", + "crypto-js": "^4.0.0", + "serialize-javascript": "^4.0.0" + } + }, + "node_modules/oidc-client/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/oidc-client/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -21656,6 +21712,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -22178,6 +22239,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -26879,6 +26945,33 @@ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, + "oidc-client": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.11.5.tgz", + "integrity": "sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==", + "requires": { + "acorn": "^7.4.1", + "base64-js": "^1.5.1", + "core-js": "^3.8.3", + "crypto-js": "^4.0.0", + "serialize-javascript": "^4.0.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "requires": { + "randombytes": "^2.1.0" + } + } + } + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", diff --git a/src/v2/client/package.json b/src/v2/client/package.json index c30ad832..4d4e529d 100644 --- a/src/v2/client/package.json +++ b/src/v2/client/package.json @@ -17,6 +17,7 @@ "bootstrap": "^5.3.1", "bootstrap-icons": "^1.11.1", "eslint": "^8.50.0", + "oidc-client": "^1.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", diff --git a/src/v2/client/src/components/LoginButton.tsx b/src/v2/client/src/components/LoginButton.tsx new file mode 100644 index 00000000..65c88b3b --- /dev/null +++ b/src/v2/client/src/components/LoginButton.tsx @@ -0,0 +1,21 @@ +import { authService } from "../services/authService"; + +export const LoginButton = () => { + const loginHandler = () => { + authService.signinRedirect(); + }; + const logoutHandler = () => { + authService.logout(); + }; + + + return ( + <> + {localStorage.getItem("LoggedInUser") === "" ? ( + + ) : ( + + )} + + ); +}; diff --git a/src/v2/client/src/components/NavBar.tsx b/src/v2/client/src/components/NavBar.tsx index fc2882ed..8b1f1ec2 100644 --- a/src/v2/client/src/components/NavBar.tsx +++ b/src/v2/client/src/components/NavBar.tsx @@ -1,4 +1,5 @@ import { Link } from "react-router-dom" +import { LoginButton } from "./LoginButton" export const NavBar = () => { return ( @@ -25,7 +26,7 @@ export const NavBar = () => { Swagger - + diff --git a/src/v2/client/src/services/authService.ts b/src/v2/client/src/services/authService.ts new file mode 100644 index 00000000..b09d46a0 --- /dev/null +++ b/src/v2/client/src/services/authService.ts @@ -0,0 +1,100 @@ +import { UserManager, WebStorageStateStore } from "oidc-client"; + +const authUrl = process.env.REACT_APP_AUTH_URL +var redirectUrl = "/landing/" +var userManager = new UserManager({ + userStore: new WebStorageStateStore({ store: window.localStorage }), + authority: + `${authUrl || "https://engineering.snow.edu/aspen/auth"}/realms/aspen/.well-known/openid-configuration`, + client_id: "aspen-web", + redirect_uri: window.location.origin + redirectUrl, + post_logout_redirect_uri: window.location.origin + "/aspen/new/", + silent_redirect_uri: window.location.origin + "/aspen/new/", + response_type: "code", + scope: "openid profile email", + loadUserInfo: true, + automaticSilentRenew: true, +}); + +userManager.startSilentRenew(); + +export const authService = { + + getUser: async () => { + const user = await userManager.getUser(); + return user; + }, + + isLoggedIn: async () => { + const user = await userManager.getUser(); + const loggedIn = user !== null && !user.expired; + return loggedIn; + }, + + signinRedirect: async () => { + + const params = new URLSearchParams(window.location.search) + var redirectUri = window.location.pathname.replace("/aspen/new", "") + + localStorage.setItem("redirectUri", redirectUri) + if (window.location.pathname === '/aspen/new/') { + localStorage.setItem("redirectUri", '/'); + } + else if(redirectUri === "/TeamDetails"){ + localStorage.setItem("redirectUri", `${redirectUri}?teamId=${params.get("teamId")}&ownerID=${params.get("ownerID")} `); + } + await userManager.signinRedirect(); + }, + + signinRedirectCallback: async () => { + const desiredDestination = localStorage.getItem("redirectUri"); + const tempDestination = desiredDestination?.replace('/login', '/'); + const user = await userManager.signinRedirectCallback(); + return { desiredDestination: tempDestination, user }; + }, + + signinSilent: async () => { + await userManager + .signinSilent() + .then((user) => { + }) + .catch((err) => { + }); + }, + + signinSilentCallback: async () => { + return await userManager.signinSilentCallback(); + }, + + createSigninRequest: async () => { + return await userManager.createSigninRequest(); + }, + + logout: async () => { + await userManager.clearStaleState(); + await userManager.signoutRedirect(); + localStorage.setItem("LoggedInUser", "") + localStorage.setItem("LoggedInEmail", "") + localStorage.setItem("access_token", "") + + // await userManager.signoutRedirect({ + // id_token_hint: localStorage.getItem("id_token"), + // }); + }, + + signoutRedirectCallback: async () => { + await userManager.signoutRedirectCallback().then(() => { + localStorage.clear(); + window.location.replace('/'); + }); + await userManager.clearStaleState(); + }, + +} + +userManager.events.addSilentRenewError((e) => { +}); + +userManager.events.addAccessTokenExpired(() => { + authService.signinSilent(); +}); \ No newline at end of file