-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invite users via email #4539
Invite users via email #4539
Changes from all commits
ffe5546
3b82feb
3aeaeed
133d46f
8a34303
b8dc6d9
ae4ab39
4a13237
52d8e26
15d28f7
c767b38
a662ab4
79d5228
0f7dda4
1812469
478ee3d
cd4f5d9
a5a3d70
5877850
66c70b7
e8f582d
ff9c006
31ea3aa
618232d
510ed56
ef22eda
7f6770d
65c6b6b
fe1e3ef
859f728
0b59b8b
ac2ee34
e93270c
1d1814e
394e30f
46ada12
6ff774c
86ea173
3be4a86
4b9c4d4
1183169
ee542d5
a29342b
151408e
d26b30e
ff35dbb
d37ee5b
5b203a3
7281f50
dc81555
c9ac6a2
2d577a2
e4917de
3026dce
fc2c398
5d823c1
490b17e
832d2a5
97ddd64
d45dc93
2d2ceee
cf01940
74ac32f
1d4f176
b579d77
76c4761
47d89d0
f44fa65
0f453c3
006bb07
c21b267
1a32f8a
af09090
d1cc308
13437da
d81cceb
1e3b666
4488b94
9a8d8e4
ffc57d8
1d2d42a
6aff7d3
9b3e248
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,15 @@ import { defineConfig } from "cypress"; | |
export default defineConfig({ | ||
e2e: { | ||
baseUrl: "http://localhost:3000", | ||
setupNodeEvents(on) { | ||
on("before:browser:launch", (browser, launchOptions) => { | ||
if (browser.family === "chromium") { | ||
// No need for tests to be slowed down by animations! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⭐ - also does it matter what browser the cypress test uses? Can we just set this across all browsers for e2e testing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's more that the |
||
launchOptions.args.push("--force-prefers-reduced-motion"); | ||
} | ||
return launchOptions; | ||
}); | ||
}, | ||
}, | ||
|
||
defaultCommandTimeout: 5000, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,17 @@ | ||
import { SYSTEM_ROUTE } from "~/features/common/nav/v2/routes"; | ||
|
||
describe("User Authentication", () => { | ||
const login = () => { | ||
cy.fixture("login.json").then((body) => { | ||
cy.intercept("POST", "/api/v1/login", body).as("postLogin"); | ||
cy.intercept("/api/v1/user/*/permission", { | ||
fixture: "user-management/permissions.json", | ||
}).as("getUserPermission"); | ||
}); | ||
|
||
cy.getByTestId("input-username").type("[email protected]"); | ||
cy.getByTestId("input-password").type("FakePassword123!{Enter}"); | ||
}; | ||
describe("when the user not logged in", () => { | ||
it("redirects them to the login page", () => { | ||
cy.visit("/"); | ||
|
@@ -20,18 +31,20 @@ describe("User Authentication", () => { | |
cy.getByTestId("Login"); | ||
|
||
cy.intercept("GET", "/api/v1/system", { body: [] }); | ||
cy.fixture("login.json").then((body) => { | ||
cy.intercept("POST", "/api/v1/login", body).as("postLogin"); | ||
cy.intercept("/api/v1/user/*/permission", { | ||
fixture: "user-management/permissions.json", | ||
}).as("getUserPermission"); | ||
}); | ||
|
||
cy.get("#email").type("[email protected]"); | ||
cy.get("#password").type("FakePassword123!{Enter}"); | ||
login(); | ||
|
||
cy.getByTestId("Home"); | ||
}); | ||
|
||
it("can persist URL after logging in", () => { | ||
cy.visit("/user-management"); | ||
cy.location("pathname").should("eq", "/login"); | ||
cy.location("search").should("eq", "?redirect=%2Fuser-management"); | ||
|
||
// Now log in | ||
login(); | ||
cy.location("pathname").should("eq", "/user-management"); | ||
}); | ||
}); | ||
|
||
describe("when the user is logged in", () => { | ||
|
@@ -70,4 +83,36 @@ describe("User Authentication", () => { | |
cy.location("pathname").should("eq", "/"); | ||
}); | ||
}); | ||
|
||
describe("invited user", () => { | ||
beforeEach(() => { | ||
cy.intercept("/api/v1/user/*/permission", { | ||
fixture: "user-management/permissions.json", | ||
}).as("getUserPermission"); | ||
cy.fixture("login.json").then((body) => { | ||
cy.intercept("POST", "/api/v1/user/accept-invite*", body).as( | ||
"postAcceptInvite" | ||
); | ||
}); | ||
}); | ||
it("can prefill email and render different copy for an invited user", () => { | ||
const data = { username: "testuser", invite_code: "123" }; | ||
const newPassword = "FakePassword123!"; | ||
cy.visit("/login", { | ||
qs: data, | ||
}); | ||
cy.getByTestId("input-username").should("be.disabled"); | ||
cy.getByTestId("input-username").should("have.value", data.username); | ||
cy.get("label").contains("Set new password"); | ||
cy.getByTestId("input-password").type(newPassword); | ||
cy.get("button").contains("Setup user").click(); | ||
cy.wait("@postAcceptInvite").then((interception) => { | ||
const { body, url } = interception.request; | ||
expect(url).to.contain(data.invite_code); | ||
expect(url).to.contain(data.username); | ||
expect(body).to.eql({ new_password: newPassword }); | ||
}); | ||
cy.getByTestId("Home"); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
"id": "123", | ||
"username": "[email protected]", | ||
"email_address": "[email protected]", | ||
"created_at": "2022-09-28T16:15:30.994Z", | ||
"first_name": "Cypress", | ||
"last_name": "User" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,30 +3,39 @@ | |
{ | ||
"id": "fid_bad38cda-476b-4d25-9883-798ba1415f40", | ||
"username": "user_3", | ||
"email_address": "[email protected]", | ||
"created_at": "2023-01-26T16:24:50.718476+00:00", | ||
"first_name": "User", | ||
"last_name": "Three" | ||
"last_name": "Three", | ||
"disabled": false | ||
}, | ||
{ | ||
"id": "fid_560cceb5-f567-4d76-a905-1e16ada1c143", | ||
"username": "user_2", | ||
"email_address": "[email protected]", | ||
"created_at": "2023-01-26T16:23:56.023966+00:00", | ||
"first_name": "User", | ||
"last_name": "Two" | ||
"last_name": "Two", | ||
"disabled": false | ||
}, | ||
{ | ||
"id": "fid_ee8f54ce-19f7-4640-b311-1cc1e77e7166", | ||
"username": "user_1", | ||
"email_address": "[email protected]", | ||
"created_at": "2023-01-26T16:16:49.575653+00:00", | ||
"first_name": "User", | ||
"last_name": "One" | ||
"last_name": "One", | ||
"disabled": true, | ||
"disabled_reason": "pending_invite" | ||
}, | ||
{ | ||
"id": "123", | ||
"username": "[email protected]", | ||
"email_address": "[email protected]", | ||
"created_at": "2022-09-28T16:15:30.994Z", | ||
"first_name": "Cypress", | ||
"last_name": "User" | ||
"last_name": "User", | ||
"disabled": false | ||
} | ||
], | ||
"total": 4, | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -69,12 +69,24 @@ const authApi = baseApi.injectEndpoints({ | |||||||||
query: () => ({ url: `oauth/role` }), | ||||||||||
providesTags: ["Roles"], | ||||||||||
}), | ||||||||||
acceptInvite: build.mutation< | ||||||||||
LoginResponse, | ||||||||||
LoginRequest & { inviteCode: string } | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah over here! fides/clients/admin-ui/src/pages/login.tsx Lines 126 to 129 in 2d2ceee
|
||||||||||
>({ | ||||||||||
query: ({ username, password, inviteCode }) => ({ | ||||||||||
url: "/user/accept-invite", | ||||||||||
params: { username, invite_code: inviteCode }, | ||||||||||
method: "POST", | ||||||||||
body: { new_password: password }, | ||||||||||
}), | ||||||||||
}), | ||||||||||
}), | ||||||||||
}); | ||||||||||
|
||||||||||
export const { | ||||||||||
useLoginMutation, | ||||||||||
useLogoutMutation, | ||||||||||
useAcceptInviteMutation, | ||||||||||
useGetRolesToScopesMappingQuery, | ||||||||||
} = authApi; | ||||||||||
export const { reducer } = authSlice; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { | ||
Button, | ||
Flex, | ||
Link, | ||
Menu, | ||
MenuButton, | ||
MenuDivider, | ||
MenuItem, | ||
MenuList, | ||
QuestionIcon, | ||
Stack, | ||
Text, | ||
UserIcon, | ||
} from "fidesui"; | ||
import { useRouter } from "next/router"; | ||
import React from "react"; | ||
|
||
import { useAppDispatch, useAppSelector } from "~/app/hooks"; | ||
import { LOGIN_ROUTE } from "~/constants"; | ||
import { logout, selectUser, useLogoutMutation } from "~/features/auth"; | ||
|
||
const useHeader = () => { | ||
const { username } = useAppSelector(selectUser) ?? { username: "" }; | ||
return { username }; | ||
}; | ||
|
||
const Header: React.FC = () => { | ||
const { username } = useHeader(); | ||
const router = useRouter(); | ||
const [logoutMutation] = useLogoutMutation(); | ||
const dispatch = useAppDispatch(); | ||
|
||
const handleLogout = async () => { | ||
await logoutMutation({}); | ||
// Go to Login page first, then dispatch logout so that ProtectedRoute does not | ||
// tack on a redirect URL. We don't need a redirect URL if we are just logging out! | ||
router.push(LOGIN_ROUTE).then(() => { | ||
dispatch(logout()); | ||
}); | ||
}; | ||
|
||
return ( | ||
<Flex | ||
as="header" | ||
height={12} | ||
width="100%" | ||
paddingX={10} | ||
flexShrink={0} | ||
alignItems="center" | ||
justifyContent="end" | ||
backgroundColor="gray.50" | ||
> | ||
<Flex alignItems="center"> | ||
<Link href="https://docs.ethyca.com" isExternal> | ||
<Button size="sm" variant="ghost"> | ||
<QuestionIcon color="gray.700" boxSize={4} /> | ||
</Button> | ||
</Link> | ||
{username && ( | ||
<Menu> | ||
<MenuButton | ||
as={Button} | ||
size="sm" | ||
variant="ghost" | ||
data-testid="header-menu-button" | ||
> | ||
<UserIcon color="gray.700" /> | ||
</MenuButton> | ||
<MenuList shadow="xl" zIndex="20"> | ||
<Stack px={3} py={2} spacing={1}> | ||
<Text fontWeight="medium">{username}</Text> | ||
</Stack> | ||
|
||
<MenuDivider /> | ||
<MenuItem | ||
_focus={{ color: "complimentary.500", bg: "gray.100" }} | ||
onClick={handleLogout} | ||
data-testid="header-menu-sign-out" | ||
> | ||
Sign out | ||
</MenuItem> | ||
</MenuList> | ||
</Menu> | ||
)} | ||
</Flex> | ||
</Flex> | ||
); | ||
}; | ||
|
||
export default Header; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa! thorough stuff. don't tell me you guys did this in the hackathon... 👀