Skip to content

Commit

Permalink
New: [AEA-4809] - Logout functionality (#300)
Browse files Browse the repository at this point in the history
## Summary

- ✨ New Feature

### Details

Adds a logout button to the header if the user is logged in. Also adds
an "Are you sure you want to log out?" page.
  • Loading branch information
wildjames authored Jan 10, 2025
1 parent 4aa07e1 commit 0d91465
Show file tree
Hide file tree
Showing 27 changed files with 887 additions and 136 deletions.
40 changes: 22 additions & 18 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind",
"source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind"
],
"runArgs": [
"--network=host"
],
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" },
"postAttachCommand": "docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v4.0.4/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && poetry run pre-commit install --install-hooks -f",
"features": {
Expand All @@ -27,24 +30,25 @@
"customizations": {
"vscode": {
"extensions": [
"AmazonWebServices.aws-toolkit-vscode",
"redhat.vscode-yaml",
"ms-python.python",
"ms-python.flake8",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"orta.vscode-jest",
"42crunch.vscode-openapi",
"mermade.openapi-lint",
"christian-kohler.npm-intellisense",
"dbaeumer.vscode-eslint",
"lfm.vscode-makefile-term",
"GrapeCity.gc-excelviewer",
"redhat.vscode-xml",
"streetsidesoftware.code-spell-checker",
"timonwong.shellcheck",
"mkhl.direnv",
"github.vscode-github-actions"
"AmazonWebServices.aws-toolkit-vscode",
"redhat.vscode-yaml",
"ms-python.python",
"ms-python.flake8",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"orta.vscode-jest",
"42crunch.vscode-openapi",
"mermade.openapi-lint",
"christian-kohler.npm-intellisense",
"dbaeumer.vscode-eslint",
"lfm.vscode-makefile-term",
"GrapeCity.gc-excelviewer",
"redhat.vscode-xml",
"streetsidesoftware.code-spell-checker",
"timonwong.shellcheck",
"mkhl.direnv",
"github.vscode-github-actions",
"Gruntfuggly.todo-tree"
],
"settings": {
"python.defaultInterpreterPath": "/workspaces/eps-prescription-tracker-ui/.venv/bin/python",
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/deploy_website_content.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ jobs:
export NEXT_PUBLIC_userPoolClientId=${userPoolClientId}
export NEXT_PUBLIC_userPoolId=${userPoolId}
export NEXT_PUBLIC_redirectSignIn="https://${fullCloudfrontDomain}/site/selectyourrole.html"
export NEXT_PUBLIC_redirectSignOut="https://${fullCloudfrontDomain}/site/"
export NEXT_PUBLIC_redirectSignOut="https://${fullCloudfrontDomain}/site/logout.html"
export NEXT_PUBLIC_COMMIT_ID=${{ inputs.COMMIT_ID }}
export NEXT_PUBLIC_TARGET_ENVIRONMENT=${{ inputs.TARGET_ENVIRONMENT }}
cd .build
make react-build
Expand Down
4 changes: 2 additions & 2 deletions .vscode/eps-prescription-tracker-ui.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"name": "packages/trackerUserInfoLambda",
"path": "../packages/trackerUserInfoLambda"
}

],
"settings": {
"jest.disabledWorkspaceFolders": [
Expand Down Expand Up @@ -144,7 +143,8 @@
},
"typescript.tsdk": "eps-prescription-tracker-ui-monorepo/node_modules/typescript/lib",
"eslint.useFlatConfig": true,
"eslint.format.enable": true
"eslint.format.enable": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"extensions": {
"recommendations": [
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ export NEXT_PUBLIC_userPoolId="eu-west-2_deadbeef"
export LOCAL_DEV=true
# DON'T TOUCH!
export API_DOMAIN_OVERRIDE=https://${SERVICE_NAME}.dev.eps.national.nhs.uk/
export NEXT_PUBLIC_TARGET_ENVIRONMENT=dev # enables mock auth
export BASE_PATH="/site" # Hosts the site at `localhost:3000/site`
export API_DOMAIN_OVERRIDE=https://${SERVICE_NAME}.dev.eps.national.nhs.uk/ # Proxies the actual deployed backend for this PR
export NEXT_PUBLIC_hostedLoginDomain=${SERVICE_NAME}.auth.eu-west-2.amazoncognito.com
export NEXT_PUBLIC_redirectSignIn=http://localhost:3000/selectyourrole/
export NEXT_PUBLIC_redirectSignOut=http://localhost:3000/
export NEXT_PUBLIC_redirectSignIn=http://localhost:3000/site/selectyourrole
export NEXT_PUBLIC_redirectSignOut=http://localhost:3000/site/logout
export NEXT_PUBLIC_COMMIT_ID="Local Development Server"
Expand All @@ -121,11 +123,12 @@ userPoolClientId=$(aws cloudformation list-exports --region eu-west-2 --query "E
userPoolId=$(aws cloudformation list-exports --region eu-west-2 --query "Exports[?Name=='${SERVICE_NAME}-stateful-resources:userPool:Id'].Value" --output text)
echo $userPoolClientId
echo $userPoolId
```

For me, the aws terminal console installed in the dev container refuses to work. Another approach is to use the browser console, accessed by clicking the terminal icon next to the search bar on the AWS web dashboard.
For me, the aws terminal console installed in the dev container refuses to work without causing a headache. Another approach is to use the browser console, accessed by clicking the terminal icon next to the search bar on the AWS web dashboard.

n.b. Ensure you've properly sourced these variables! Direnv can sometimes miss changes.
n.b. Ensure you've properly sourced these variables! `direnv` can sometimes miss changes on my machine.
```
source .envrc
```
Expand Down
21 changes: 14 additions & 7 deletions packages/cdk/resources/Cognito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,31 @@ export class Cognito extends Construct {
}

const callbackUrls = [
`https://${props.fullCloudfrontDomain}/site/`,
`https://${props.fullCloudfrontDomain}/site/selectyourrole`,
// FIXME: This is temporary, until we get routing fixed
`https://${props.fullCloudfrontDomain}/site/selectyourrole.html`,
`https://${props.fullCloudfrontDomain}/auth_demo/`,
`https://${props.fullCloudfrontDomain}/site/selectyourrole/`,
// TODO: This is for the proof-of-concept login page, and can probably be deleted soon.
`https://${props.fullCloudfrontDomain}/auth_demo`,
`https://${props.fullCloudfrontDomain}/oauth2/idpresponse`
]

const logoutUrls = [
`https://${props.fullCloudfrontDomain}/site/`,
`https://${props.fullCloudfrontDomain}/site/auth_demo.html`,
`https://${props.fullCloudfrontDomain}/auth_demo/`
`https://${props.fullCloudfrontDomain}/site/logout`,
`https://${props.fullCloudfrontDomain}/site/logout.html`,
`https://${props.fullCloudfrontDomain}/auth_demo`
]

if (props.useLocalhostCallback) {
// Local, without base path set
callbackUrls.push("http://localhost:3000/selectyourrole/")
logoutUrls.push("http://localhost:3000/logout/")
// Local, with base path set to /site
logoutUrls.push("http://localhost:3000/site/logout/")
callbackUrls.push("http://localhost:3000/site/selectyourrole/")
// Auth demo stuff
callbackUrls.push("http://localhost:3000/auth/")
callbackUrls.push("http://localhost:3000/auth_demo/")
callbackUrls.push("http://localhost:3000/selectyourrole/")
// Root path, just in case
logoutUrls.push("http://localhost:3000/")
}
// add the web client
Expand Down
95 changes: 95 additions & 0 deletions packages/cpt-ui/__tests__/EpsModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import { EpsModal } from "@/components/EpsModal";

describe("EpsModal", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("does not render the modal when isOpen is false", () => {
render(
<EpsModal isOpen={false} onClose={jest.fn()}>
<div>Modal Content</div>
</EpsModal>
);
// The content should not be in the document
expect(screen.queryByText(/Modal Content/i)).not.toBeInTheDocument();
});

test("renders the modal when isOpen is true", () => {
render(
<EpsModal isOpen={true} onClose={jest.fn()}>
<div>Modal Content</div>
</EpsModal>
);
// The content should appear in the document
expect(screen.getByText(/Modal Content/i)).toBeInTheDocument();
});

test("calls onClose when user clicks outside modal content", () => {
const onCloseMock = jest.fn();
render(
<EpsModal isOpen={true} onClose={onCloseMock}>
<div data-testid="modal-content">Modal Content</div>
</EpsModal>
);

const overlay = screen.getByTestId("eps-modal-overlay");
const modalContent = screen.getByTestId("modal-content");

// Clicking directly on the content should NOT trigger onClose
fireEvent.click(modalContent);
expect(onCloseMock).not.toHaveBeenCalled();

// Clicking on the overlay (outside the content) should trigger onClose
fireEvent.click(overlay);
expect(onCloseMock).toHaveBeenCalledTimes(1);
});

test("calls onClose when user clicks the close button", () => {
const onCloseMock = jest.fn();
render(
<EpsModal isOpen={true} onClose={onCloseMock}>
<div>Modal Content</div>
<button onClick={onCloseMock}>TEST CLOSE BUTTON</button>
</EpsModal>
);

const closeButton = screen.getByText(/TEST CLOSE BUTTON/);
fireEvent.click(closeButton);
expect(onCloseMock).toHaveBeenCalledTimes(1);
});

test("calls onClose when user presses Escape", () => {
const onCloseMock = jest.fn();
render(
<EpsModal isOpen={true} onClose={onCloseMock}>
<div>Modal Content</div>
</EpsModal>
);

// Fire 'Escape' keydown event on window
fireEvent.keyDown(window, { key: "Escape" });
expect(onCloseMock).toHaveBeenCalledTimes(1);
});

test("calls onClose when user presses Enter or Space on the backdrop", () => {
const onCloseMock = jest.fn();
render(
<EpsModal isOpen={true} onClose={onCloseMock}>
<div>Modal Content</div>
</EpsModal>
);

const overlay = screen.getByTestId("eps-modal-overlay");

fireEvent.keyDown(overlay, { key: "Enter" });
expect(onCloseMock).toHaveBeenCalledTimes(1);

// Fire again with ' '
fireEvent.keyDown(overlay, { key: " " });
expect(onCloseMock).toHaveBeenCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
import React, { useState } from "react";

// Mock the configureAmplify module
jest.mock("../context/configureAmplify", () => ({
jest.mock("@/context/configureAmplify", () => ({
__esModule: true,
authConfig: {
Auth: {
Expand Down Expand Up @@ -74,10 +74,14 @@ const MockAuthProvider = ({ children }) => {

// Since we've referenced AuthContext in the mock provider, we need to re-import it here
// after the mock is set up.
import { AuthContext } from "../context/AuthProvider";
import AuthPage from "../app/auth_demo/page";
import { AuthContext } from "@/context/AuthProvider";
import AuthPage from "@/app/login/page";

describe("AuthPage", () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_TARGET_ENVIRONMENT = "dev";
});

it("renders the page and the main buttons", () => {
const { container } = render(
<MockAuthProvider>
Expand Down Expand Up @@ -179,4 +183,17 @@ describe("AuthPage", () => {
screen.getByText((content) => content.includes('"isSignedIn": false'))
).toBeInTheDocument();
});

it("shows a spinner when not in a mock auth environment", () => {
process.env.NEXT_PUBLIC_TARGET_ENVIRONMENT = "prod";

render(
<MockAuthProvider>
<AuthPage />
</MockAuthProvider>
);

const spinner = screen.getByRole("heading", { name: /Redirecting to CIS2 login page.../i });
expect(spinner).toBeInTheDocument();
});
});
Loading

0 comments on commit 0d91465

Please sign in to comment.