Skip to content

Commit 5381e13

Browse files
wildjamesanthony-nhsdependabot[bot]
authored
New: [AEA-4568] - Call the Cognito flow (#224)
## Summary - ✨ New Feature - ### Details This will add a context to the CPT-UI package, that will allow pages to interact with the auth layer in a consistent way. --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: anthony-nhs <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 6c01aa3 commit 5381e13

File tree

14 files changed

+793
-25
lines changed

14 files changed

+793
-25
lines changed

.github/workflows/deploy_website_content.yml

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,40 @@ jobs:
5555
mkdir -p .build
5656
tar -xf artifact.tar -C .build
5757
58+
- name: Set Environment Variables
59+
id: setup-env
60+
run: |
61+
hostedLoginDomain=$(aws cloudformation list-exports --region us-east-1 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-us-certs:fullCognitoDomain:Name'].Value" --output text)
62+
userPoolClientId=$(aws cloudformation list-exports --region eu-west-2 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-stateful-resources:userPoolClient:userPoolClientId'].Value" --output text)
63+
userPoolId=$(aws cloudformation list-exports --region eu-west-2 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-stateful-resources:userPool:Id'].Value" --output text)
64+
fullCloudfrontDomain=$(aws cloudformation list-exports --region us-east-1 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-us-certs:fullCloudfrontDomain:Name'].Value" --output text)
65+
66+
{
67+
echo "hostedLoginDomain=${hostedLoginDomain}"
68+
echo "userPoolClientId=${userPoolClientId}"
69+
echo "userPoolId=${userPoolId}"
70+
echo "fullCloudfrontDomain=${fullCloudfrontDomain}"
71+
} >> "$GITHUB_ENV"
72+
5873
- name: build react app
5974
run: |
75+
export NEXT_PUBLIC_hostedLoginDomain=${hostedLoginDomain}
76+
export NEXT_PUBLIC_userPoolClientId=${userPoolClientId}
77+
export NEXT_PUBLIC_userPoolId=${userPoolId}
78+
export NEXT_PUBLIC_redirectSignIn="https://${fullCloudfrontDomain}/site/auth_demo.html"
79+
export NEXT_PUBLIC_redirectSignOut="https://${fullCloudfrontDomain}/site/"
80+
export NEXT_PUBLIC_COMMIT_ID=${{ inputs.COMMIT_ID }}
81+
6082
cd .build
6183
make react-build
6284
6385
- name: build auth_demo react app (temp step for testing)
6486
run: |
65-
REACT_APP_hostedLoginDomain=$(aws cloudformation list-exports --region us-east-1 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-us-certs:fullCognitoDomain:Name'].Value" --output text)
66-
REACT_APP_userPoolClientId=$(aws cloudformation list-exports --region eu-west-2 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-stateful-resources:userPoolClient:userPoolClientId'].Value" --output text)
67-
REACT_APP_userPoolId=$(aws cloudformation list-exports --region eu-west-2 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-stateful-resources:userPool:Id'].Value" --output text)
68-
fullCloudfrontDomain=$(aws cloudformation list-exports --region us-east-1 --query "Exports[?Name=='${{ inputs.SERVICE_NAME }}-us-certs:fullCloudfrontDomain:Name'].Value" --output text)
69-
REACT_APP_redirectSignIn="https://${fullCloudfrontDomain}/auth_demo/"
87+
export REACT_APP_hostedLoginDomain=${hostedLoginDomain}
88+
export REACT_APP_userPoolClientId=${userPoolClientId}
89+
export REACT_APP_userPoolId=${userPoolId}
90+
export REACT_APP_redirectSignIn="https://${fullCloudfrontDomain}/auth_demo/"
7091
71-
export REACT_APP_hostedLoginDomain
72-
export REACT_APP_userPoolClientId
73-
export REACT_APP_userPoolId
74-
export REACT_APP_redirectSignIn
7592
cd .build
7693
make auth_demo_build
7794

packages/cdk/resources/Cognito.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,19 @@ export class Cognito extends Construct {
187187

188188
const callbackUrls = [
189189
`https://${props.fullCloudfrontDomain}/site/`,
190+
`https://${props.fullCloudfrontDomain}/site/auth_demo.html`,
190191
`https://${props.fullCloudfrontDomain}/auth_demo/`
191192
]
192193

193194
const logoutUrls = [
194195
`https://${props.fullCloudfrontDomain}/site/`,
196+
`https://${props.fullCloudfrontDomain}/site/auth_demo.html`,
195197
`https://${props.fullCloudfrontDomain}/auth_demo/`
196198
]
197199

198200
if (props.useLocalhostCallback) {
199201
callbackUrls.push( "http://localhost:3000/auth/")
202+
callbackUrls.push( "http://localhost:3000/auth_demo/")
200203
logoutUrls.push( "http://localhost:3000/")
201204
}
202205
// add the web client
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// @ts-nocheck
2+
import "@testing-library/jest-dom";
3+
import { render, screen, waitFor } from "@testing-library/react";
4+
import userEvent from "@testing-library/user-event";
5+
import React, { useState } from "react";
6+
7+
// Mock the configureAmplify module
8+
jest.mock("../context/configureAmplify", () => ({
9+
__esModule: true,
10+
authConfig: {
11+
Auth: {
12+
Cognito: {
13+
userPoolClientId: "mockUserPoolClientId",
14+
userPoolId: "mockUserPoolId",
15+
loginWith: {
16+
oauth: {
17+
domain: "mockHostedLoginDomain",
18+
scopes: ["openid", "email", "phone", "profile", "aws.cognito.signin.user.admin"],
19+
redirectSignIn: ["mockRedirectSignIn"],
20+
redirectSignOut: ["mockRedirectSignOut"],
21+
responseType: "code",
22+
},
23+
username: true,
24+
email: false,
25+
phone: false,
26+
},
27+
},
28+
},
29+
},
30+
}));
31+
32+
// Create a mock AuthContext provider that allows us to control the state
33+
const mockCognitoSignIn = jest.fn();
34+
const mockCognitoSignOut = jest.fn();
35+
36+
const MockAuthProvider = ({ children }) => {
37+
// State to simulate auth changes
38+
const [authState, setAuthState] = useState({
39+
isSignedIn: false,
40+
user: null,
41+
error: null,
42+
idToken: null,
43+
accessToken: null,
44+
cognitoSignIn: async (options: { provider: { custom: any; }; }) => {
45+
mockCognitoSignIn(options);
46+
// Simulate a sign-in update
47+
setAuthState((prev) => ({
48+
...prev,
49+
isSignedIn: true,
50+
user: { username: options?.provider?.custom || "mockUser" },
51+
error: null,
52+
idToken: "mockIdToken",
53+
accessToken: "mockAccessToken",
54+
}));
55+
},
56+
cognitoSignOut: async () => {
57+
mockCognitoSignOut();
58+
// Simulate a sign-out update
59+
setAuthState((prev) => ({
60+
...prev,
61+
isSignedIn: false,
62+
user: null,
63+
error: null,
64+
idToken: null,
65+
accessToken: null,
66+
}));
67+
},
68+
});
69+
70+
return (
71+
<AuthContext.Provider value={authState}>{children}</AuthContext.Provider>
72+
);
73+
};
74+
75+
// Since we've referenced AuthContext in the mock provider, we need to re-import it here
76+
// after the mock is set up.
77+
import { AuthContext } from "../context/AuthContext";
78+
import AuthPage from "../app/auth_demo/page";
79+
80+
describe("AuthPage", () => {
81+
it("renders the page and the main buttons", () => {
82+
const { container } = render(
83+
<MockAuthProvider>
84+
<AuthPage />
85+
</MockAuthProvider>
86+
);
87+
88+
const heading = screen.getByRole("heading", { level: 1 });
89+
expect(heading).toBeInTheDocument();
90+
91+
const primaryLogin = container.querySelector("#primary-signin");
92+
const mockLogin = container.querySelector("#mock-signin");
93+
const signout = container.querySelector("#signout");
94+
95+
expect(primaryLogin).toBeInTheDocument();
96+
expect(mockLogin).toBeInTheDocument();
97+
expect(signout).toBeInTheDocument();
98+
});
99+
100+
it("calls cognitoSignIn with 'Primary' when the primary login button is clicked", async () => {
101+
render(
102+
<MockAuthProvider>
103+
<AuthPage />
104+
</MockAuthProvider>
105+
);
106+
107+
const primaryLogin = screen.getByRole("button", { name: /Log in with PTL CIS2/i });
108+
109+
await userEvent.click(primaryLogin);
110+
111+
await waitFor(() => {
112+
expect(mockCognitoSignIn).toHaveBeenCalledWith({
113+
provider: { custom: "Primary" },
114+
});
115+
});
116+
117+
// After sign-in, check if the user details are displayed
118+
expect(screen.getByText(/username: Primary/i)).toBeInTheDocument();
119+
expect(
120+
screen.getByText((content) => content.includes('"isSignedIn": true'))
121+
).toBeInTheDocument();
122+
expect(screen.getByText(/idToken: mockIdToken/i)).toBeInTheDocument();
123+
expect(screen.getByText(/accessToken: mockAccessToken/i)).toBeInTheDocument();
124+
});
125+
126+
it("calls cognitoSignIn with 'Mock' when the mock login button is clicked", async () => {
127+
render(
128+
<MockAuthProvider>
129+
<AuthPage />
130+
</MockAuthProvider>
131+
);
132+
133+
const mockLogin = screen.getByRole("button", { name: /Log in with mock CIS2/i });
134+
135+
await userEvent.click(mockLogin);
136+
137+
await waitFor(() => {
138+
expect(mockCognitoSignIn).toHaveBeenCalledWith({
139+
provider: { custom: "Mock" },
140+
});
141+
});
142+
143+
// After sign-in with Mock, check if the username displayed is "Mock"
144+
expect(screen.getByText(/username: Mock/i)).toBeInTheDocument();
145+
expect(
146+
screen.getByText((content) => content.includes('"isSignedIn": true'))
147+
).toBeInTheDocument();
148+
expect(screen.getByText(/idToken: mockIdToken/i)).toBeInTheDocument();
149+
expect(screen.getByText(/accessToken: mockAccessToken/i)).toBeInTheDocument();
150+
});
151+
152+
it("calls cognitoSignOut when the sign out button is clicked", async () => {
153+
render(
154+
<MockAuthProvider>
155+
<AuthPage />
156+
</MockAuthProvider>
157+
);
158+
159+
// First sign in to have a user state
160+
const primaryLogin = screen.getByRole("button", { name: /Log in with PTL CIS2/i });
161+
await userEvent.click(primaryLogin);
162+
163+
await waitFor(() => {
164+
expect(mockCognitoSignIn).toHaveBeenCalled();
165+
expect(
166+
screen.getByText((content) => content.includes('"isSignedIn": true'))
167+
).toBeInTheDocument();
168+
});
169+
170+
const signOutBtn = screen.getByRole("button", { name: /Sign Out/i });
171+
await userEvent.click(signOutBtn);
172+
173+
await waitFor(() => {
174+
expect(mockCognitoSignOut).toHaveBeenCalled();
175+
});
176+
177+
// After sign-out, the user should be null, tokens cleared, and isSignedIn false
178+
expect(
179+
screen.getByText((content) => content.includes('"isSignedIn": false'))
180+
).toBeInTheDocument();
181+
});
182+
});

0 commit comments

Comments
 (0)