Skip to content

Commit

Permalink
Merge pull request #2906 from HHS/OPS-2737-azure-ams-fe-config-2
Browse files Browse the repository at this point in the history
Ops 2737 azure ams fe config 2
  • Loading branch information
johndeange authored Oct 10, 2024
2 parents 3869336 + 7d11e24 commit ed4974e
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 19 deletions.
1 change: 0 additions & 1 deletion backend/data_tools/initial_data/004-role_version.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, tra
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (2, 'user', '{GET_AGREEMENT,PUT_AGREEMENT,PATCH_AGREEMENT,POST_AGREEMENT,GET_BUDGET_LINE_ITEM,PUT_BUDGET_LINE_ITEM,PATCH_BUDGET_LINE_ITEM,POST_BUDGET_LINE_ITEM,GET_SERVICES_COMPONENT,PUT_SERVICES_COMPONENT,PATCH_SERVICES_COMPONENT,POST_SERVICES_COMPONENT,GET_BLI_PACKAGE,PUT_BLI_PACKAGE,PATCH_BLI_PACKAGE,POST_BLI_PACKAGE,GET_CAN,GET_DIVISION,GET_NOTIFICATION,PUT_NOTIFICATION,PATCH_NOTIFICATION,GET_PORTFOLIO,GET_RESEARCH_PROJECT,POST_RESEARCH_PROJECT,GET_USER,GET_USERS,GET_HISTORY,GET_WORKFLOW,GET_CHANGE_REQUEST,PATCH_CHANGE_REQUEST,POST_CHANGE_REQUEST,GET_CHANGE_REQUEST_REVIEW}', current_timestamp, current_timestamp, 1, null, 0);
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (3, 'unassigned', '{GET_USER,POST_USER,PUT_USER,PATCH_USER}', current_timestamp, current_timestamp, 1, null, 0);
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (4, 'division-director', '{GET_AGREEMENT,PUT_AGREEMENT,PATCH_AGREEMENT,POST_AGREEMENT,GET_BUDGET_LINE_ITEM,PUT_BUDGET_LINE_ITEM,PATCH_BUDGET_LINE_ITEM,POST_BUDGET_LINE_ITEM,GET_SERVICES_COMPONENT,PUT_SERVICES_COMPONENT,PATCH_SERVICES_COMPONENT,POST_SERVICES_COMPONENT,GET_BLI_PACKAGE,PUT_BLI_PACKAGE,PATCH_BLI_PACKAGE,POST_BLI_PACKAGE,GET_CAN,GET_DIVISION,GET_NOTIFICATION,PUT_NOTIFICATION,PATCH_NOTIFICATION,GET_PORTFOLIO,GET_RESEARCH_PROJECT,POST_RESEARCH_PROJECT,GET_USER,GET_USERS,GET_HISTORY,GET_WORKFLOW,PUT_WORKFLOW,PATCH_WORKFLOW,POST_WORKFLOW,GET_CHANGE_REQUEST,PATCH_CHANGE_REQUEST,POST_CHANGE_REQUEST,GET_CHANGE_REQUEST_REVIEW,PATCH_CHANGE_REQUEST_REVIEW,POST_CHANGE_REQUEST_REVIEW}', current_timestamp, current_timestamp, 1, null, 0);
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (4, 'division-director', '{GET_AGREEMENT,PUT_AGREEMENT,PATCH_AGREEMENT,POST_AGREEMENT,GET_BUDGET_LINE_ITEM,PUT_BUDGET_LINE_ITEM,PATCH_BUDGET_LINE_ITEM,POST_BUDGET_LINE_ITEM,GET_SERVICES_COMPONENT,PUT_SERVICES_COMPONENT,PATCH_SERVICES_COMPONENT,POST_SERVICES_COMPONENT,GET_BLI_PACKAGE,PUT_BLI_PACKAGE,PATCH_BLI_PACKAGE,POST_BLI_PACKAGE,GET_CAN,GET_DIVISION,GET_NOTIFICATION,PUT_NOTIFICATION,PATCH_NOTIFICATION,GET_PORTFOLIO,GET_RESEARCH_PROJECT,POST_RESEARCH_PROJECT,GET_USER,GET_USERS,GET_HISTORY,GET_WORKFLOW,PUT_WORKFLOW,PATCH_WORKFLOW,POST_WORKFLOW,GET_CHANGE_REQUEST,PATCH_CHANGE_REQUEST,POST_CHANGE_REQUEST,GET_CHANGE_REQUEST_REVIEW,PATCH_CHANGE_REQUEST_REVIEW,POST_CHANGE_REQUEST_REVIEW}', current_timestamp, current_timestamp, 1, null, 0);
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (5, 'USER_ADMIN', '{POST_USER}', current_timestamp, current_timestamp, 1, null, 0);
INSERT INTO ops.role_version (id, name, permissions, created_on, updated_on, transaction_id, end_transaction_id, operation_type) VALUES (6, 'BUDGET_TEAM', '{GET_AGREEMENT,PUT_AGREEMENT,PATCH_AGREEMENT,POST_AGREEMENT,GET_BUDGET_LINE_ITEM,PUT_BUDGET_LINE_ITEM,PATCH_BUDGET_LINE_ITEM,POST_BUDGET_LINE_ITEM,GET_SERVICES_COMPONENT,PUT_SERVICES_COMPONENT,PATCH_SERVICES_COMPONENT,POST_SERVICES_COMPONENT,GET_BLI_PACKAGE,PUT_BLI_PACKAGE,PATCH_BLI_PACKAGE,POST_BLI_PACKAGE,GET_CAN,PUT_CAN,PATCH_CAN,POST_CAN,GET_DIVISION,GET_NOTIFICATION,PUT_NOTIFICATION,PATCH_NOTIFICATION,GET_PORTFOLIO,PUT_PORTFOLIO,PATCH_PORTFOLIO,POST_PORTFOLIO,GET_RESEARCH_PROJECT,POST_RESEARCH_PROJECT,PUT_RESEARCH_PROJECT,POST_RESEARCH_PROJECT,GET_USER,GET_HISTORY,GET_CHANGE_REQUEST,PUT_CHANGE_REQUEST,PATCH_CHANGE_REQUEST,POST_CHANGE_REQUEST,GET_CHANGE_REQUEST_REVIEW,PUT_CHANGE_REQUEST_REVIEW,PATCH_CHANGE_REQUEST_REVIEW,POST_CHANGE_REQUEST_REVIEW,GET_UPLOAD_DOCUMENT,POST_UPLOAD_DOCUMENT}', current_timestamp, current_timestamp, 1, null, 0);
3 changes: 0 additions & 3 deletions backend/data_tools/initial_data/015-ops_user.sql

This file was deleted.

3 changes: 0 additions & 3 deletions backend/data_tools/initial_data/016-ops_user_version.sql

This file was deleted.

5 changes: 0 additions & 5 deletions backend/data_tools/initial_data/017-user_role.sql

This file was deleted.

5 changes: 0 additions & 5 deletions backend/data_tools/initial_data/018-user_role_version.sql

This file was deleted.

2 changes: 1 addition & 1 deletion backend/data_tools/scripts/initial_data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ else
echo "Local environment detected. Loading seed data..."
for file in $(ls ./data_tools/initial_data/*.sql | sort -g); do
echo "Loading $file..."
psql postgresql://"$ADMIN_PGUSER":"$ADMIN_PGPASSWORD"@"$PGHOST":"$PGPORT"/"$PGDATABASE" -f $file
psql postgresql://"$PGUSER":"$PGPASSWORD"@"$PGHOST":"$PGPORT"/"$PGDATABASE" -f $file
done
fi

Expand Down
210 changes: 210 additions & 0 deletions frontend/src/components/Auth/MultiAuthSectionWithDebugging.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import React, { useState } from "react";
import { login } from "./authSlice";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import cryptoRandomString from "crypto-random-string";
import { getAccessToken, getAuthorizationCode, setActiveUser } from "./auth";
import { apiLogin } from "../../api/apiLogin";
import ContainerModal from "../UI/Modals/ContainerModal";

const MultiAuthSectionWithDebugging = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [showModal, setShowModal] = useState(false);
const [debugInfo, setDebugInfo] = useState([]);

const addDebugInfo = (info) => {
setDebugInfo((prev) => [...prev, info]);
};

const callBackend = React.useCallback(
async (authCode) => {
console.log(`Received Authentication Code = ${authCode}`);
const activeProvider = localStorage.getItem("activeProvider");
if (activeProvider === null || activeProvider === undefined) {
addDebugInfo("API Login Failed! No Active Provider");
}

let response;
try {
response = await apiLogin(activeProvider, authCode);
// eslint-disable-next-line no-unused-vars
} catch (error) {
addDebugInfo("Error logging in");
}

const access_token = response.access_token;
const refresh_token = response.refresh_token;

if (access_token === null || access_token === undefined) {
addDebugInfo("API Login Failed!");
} else {
// TODO: We should try to move the access_token to a secure cookie,
// which will require a bit of re-work, since we won't have access to
// the data within the cookie; instead will need to do additional API calls
// to get the data we need.
addDebugInfo(`Access Token = ${access_token}`);
addDebugInfo(`Refresh Token = ${refresh_token}`);
localStorage.setItem("access_token", access_token);
localStorage.setItem("refresh_token", refresh_token);
await dispatch(login());
await setActiveUser(access_token, dispatch);
}
},
[dispatch, navigate]
);

React.useEffect(() => {
const currentJWT = getAccessToken();
if (currentJWT) {
// TODO: we should validate the JWT here and set it on state if valid else logout
dispatch(login());
setActiveUser(currentJWT, dispatch);
return;
}

const localStateString = localStorage.getItem("ops-state-key");
if (localStateString) {
const queryParams = new URLSearchParams(window.location.search);
addDebugInfo(`Current URL = ${window.location.href}`);
addDebugInfo(`Query Params = ${queryParams}`);

// check if we have been redirected here from the OIDC provider
if (queryParams.has("state") && queryParams.has("code")) {
// check that the state matches
const returnedState = queryParams.get("state");
const localStateString = localStorage.getItem("ops-state-key");

localStorage.removeItem("ops-state-key");

if (localStateString !== returnedState) {
addDebugInfo("Response from OIDC provider is invalid.");
throw new Error("Response from OIDC provider is invalid.");
} else {
const authCode = queryParams.get("code");
addDebugInfo(`Auth Code = ${authCode}`);
callBackend(authCode).catch(console.error);
}
}
} else {
// first page load - generate state token and set on localStorage
localStorage.setItem("ops-state-key", cryptoRandomString({ length: 64 }));
}
}, [callBackend, dispatch]);

// TODO: Replace these tokens with config variables, that can be passed in at deploy-time,
// So that we don't actually store anything in code.
const handleFakeAuthLogin = (user_type) => {
localStorage.setItem("activeProvider", "fakeauth");
callBackend(user_type).catch(console.error);
};

const handleSSOLogin = (provider) => {
localStorage.setItem("activeProvider", provider);
window.location.href = getAuthorizationCode(provider, localStorage.getItem("ops-state-key"));
};

return (
<>
<div className="bg-white padding-y-3 padding-x-5 border border-base-lighter">
<h1 className="margin-bottom-1">Sign in to your account</h1>
<div className="usa-prose">
<p className="margin-top-1">
You can access your account by signing in with one of the options below.
</p>
</div>
{import.meta.env.MODE === "development" && ( // login.gov is only configured to work locally at the moment
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleSSOLogin("logingov")}
>
Sign in with Login.gov
</button>
</p>
)}
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleSSOLogin("hhsams")}
>
Sign in with HHS AMS
</button>
</p>
{!import.meta.env.PROD && (
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => setShowModal(true)}
>
Sign in with FakeAuth®
</button>
</p>
)}
{showModal && (
<ContainerModal
heading="FakeAuth® User Selection"
description="Please select the User role you would like to assume."
setShowModal={setShowModal}
>
<div className="usa-prose">
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleFakeAuthLogin("admin_user")}
>
Admin User
</button>
</p>
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleFakeAuthLogin("budget_team")}
>
Budget Team Member
</button>
</p>
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleFakeAuthLogin("division_director")}
>
Division Director
</button>
</p>
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleFakeAuthLogin("basic_user")}
>
Basic User
</button>
</p>
<p>
<button
className="usa-button usa-button--outline width-full"
onClick={() => handleFakeAuthLogin("new_user")}
disabled={true}
>
New User
</button>
</p>
</div>
</ContainerModal>
)}
</div>
{debugInfo.length > 0 && (
<div className="usa-prose">
<h2>Debug Information</h2>
<ul>
{debugInfo.map((info, index) => (
<li key={index}>{info}</li>
))}
</ul>
</div>
)}
</>
);
};

export default MultiAuthSectionWithDebugging;
6 changes: 5 additions & 1 deletion frontend/src/pages/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import MultiAuthSection from "../components/Auth/MultiAuthSection";
import Footer from "../components/UI/Footer";
import logo from "../components/UI/Header/OPRE_Logo.png";
import MultiAuthSectionWithDebugging from "../components/Auth/MultiAuthSectionWithDebugging.jsx";

const debuggingEnabled = import.meta.env.VITE_AUTH_DEBUG && !window.location.href.startsWith("https:/stg");

function Login() {
const styles = {
Expand Down Expand Up @@ -43,7 +46,8 @@ function Login() {
<section className="grid-container usa-section">
<div className="grid-row margin-x-neg-205 margin-bottom-6 flex-justify-center">
<div className="grid-col-6 padding-x-205 margin-bottom-4">
<MultiAuthSection />
{debuggingEnabled && <MultiAuthSectionWithDebugging />}
{!debuggingEnabled && <MultiAuthSection />}
</div>
<div className="grid-col-6 padding-x-205">
<h2>Access to OPS requires proper authentication.</h2>
Expand Down
1 change: 1 addition & 0 deletions frontend/vite-env/env.dev.local
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
VITE_HHSAMS_CLIENT_ID=44fe2c7a-e9c5-43ec-87e9-3de78d2d3a11
VITE_HHSAMS_AUTH_ENDPOINT=https://sso-stage.acf.hhs.gov/auth/realms/ACF-SSO/protocol/openid-connect/auth
VITE_HHSAMS_LOGOUT_ENDPOINT=https://sso-stage.acf.hhs.gov/auth/realms/ACF-SSO/protocol/openid-connect/logout
VITE_AUTH_DEBUG=1
1 change: 1 addition & 0 deletions frontend/vite-env/env.production.local
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
VITE_HHSAMS_CLIENT_ID=https://ops.opre.acf.gov
VITE_HHSAMS_AUTH_ENDPOINT=https://sso.acf.hhs.gov/auth/realms/ACF-AMS/protocol/openid-connect/auth
VITE_HHSAMS_LOGOUT_ENDPOINT=https://sso.acf.hhs.gov/auth/realms/ACF-AMS/protocol/openid-connect/logout
VITE_AUTH_DEBUG=1

0 comments on commit ed4974e

Please sign in to comment.