-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #368 from huwshimi/oidc-login
WD-13638 - feat: log in via OIDC
- Loading branch information
Showing
27 changed files
with
585 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { fetchMe } from "./auth"; | ||
|
||
beforeEach(() => { | ||
fetchMock.resetMocks(); | ||
}); | ||
|
||
test("fetches a user", async () => { | ||
const user = { | ||
email: "email", | ||
name: "name", | ||
nonce: "nonce", | ||
sid: "sid", | ||
sub: "sub", | ||
}; | ||
fetchMock.mockResponse(JSON.stringify(user), { status: 200 }); | ||
await expect(fetchMe()).resolves.toStrictEqual(user); | ||
}); | ||
|
||
test("handles a non-authenticated user", async () => { | ||
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 401 }); | ||
await expect(fetchMe()).resolves.toBeNull(); | ||
}); | ||
|
||
test("catches errors", async () => { | ||
const error = "Uh oh!"; | ||
fetchMock.mockRejectedValue(error); | ||
await expect(fetchMe()).rejects.toBe(error); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { apiBasePath } from "util/basePaths"; | ||
import type { UserPrincipal } from "types/auth"; | ||
import { handleResponse } from "util/api"; | ||
|
||
const BASE = `${apiBasePath}auth`; | ||
|
||
export const authURLs = { | ||
login: BASE, | ||
me: `${BASE}/me`, | ||
}; | ||
|
||
export const fetchMe = (): Promise<UserPrincipal> => { | ||
return new Promise((resolve, reject) => { | ||
fetch(authURLs.me) | ||
.then((response: Response) => | ||
// If the user is not authenticated then return null instead of throwing an | ||
// error. This is necessary so that a login screen can be displayed instead of displaying | ||
// the error. | ||
[401, 403].includes(response.status) ? null : handleResponse(response), | ||
) | ||
.then((result: UserPrincipal) => resolve(result)) | ||
.catch(reject); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { renderComponent } from "test/utils"; | ||
import Layout from "./Layout"; | ||
import { screen } from "@testing-library/dom"; | ||
import { LoginLabel } from "components/Login"; | ||
|
||
beforeEach(() => { | ||
fetchMock.resetMocks(); | ||
}); | ||
|
||
test("displays the login screen if the user is not authenticated", async () => { | ||
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 403 }); | ||
renderComponent(<Layout />); | ||
expect( | ||
await screen.findByRole("heading", { name: LoginLabel.TITLE }), | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
test("displays the layout and content if the user is authenticated", async () => { | ||
const user = { | ||
email: "email", | ||
name: "name", | ||
nonce: "nonce", | ||
sid: "sid", | ||
sub: "sub", | ||
}; | ||
fetchMock.mockResponse(JSON.stringify(user), { status: 200 }); | ||
renderComponent(<Layout />, { | ||
path: "/", | ||
url: "/", | ||
routeChildren: [ | ||
{ | ||
element: <h1>Content</h1>, | ||
path: "/", | ||
}, | ||
], | ||
}); | ||
expect(await screen.findByRole("heading", { name: "Content" })); | ||
expect(document.querySelector("#app-layout")).toBeInTheDocument(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { FC, Suspense } from "react"; | ||
import { Outlet } from "react-router-dom"; | ||
import { ApplicationLayout } from "@canonical/react-components"; | ||
import Loader from "components/Loader"; | ||
import Login from "components/Login"; | ||
import Logo from "components/Logo"; | ||
import Navigation from "components/Navigation"; | ||
import Panels from "components/Panels"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { queryKeys } from "util/queryKeys"; | ||
import { fetchMe } from "api/auth"; | ||
|
||
const Layout: FC = () => { | ||
const { | ||
data: user, | ||
isLoading, | ||
error, | ||
} = useQuery({ | ||
queryKey: [queryKeys.auth], | ||
queryFn: fetchMe, | ||
}); | ||
return user ? ( | ||
<ApplicationLayout | ||
aside={<Panels />} | ||
id="app-layout" | ||
logo={<Logo />} | ||
sideNavigation={<Navigation username={user.name || user.email} />} | ||
> | ||
<Suspense fallback={<Loader />}> | ||
<Outlet /> | ||
</Suspense> | ||
</ApplicationLayout> | ||
) : ( | ||
<Login isLoading={isLoading} error={error?.message}></Login> | ||
); | ||
}; | ||
|
||
export default Layout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./Layout"; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { renderComponent } from "test/utils"; | ||
import Login from "./Login"; | ||
import { screen, within } from "@testing-library/dom"; | ||
|
||
test("displays the loading state", () => { | ||
renderComponent(<Login isLoading />); | ||
expect( | ||
within(screen.getByRole("alert")).getByText("Loading"), | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
test("displays errors", () => { | ||
const error = "Uh oh!"; | ||
renderComponent(<Login error={error} />); | ||
expect(within(screen.getByRole("code")).getByText(error)).toBeInTheDocument(); | ||
}); | ||
|
||
test("displays the login button", () => { | ||
renderComponent(<Login />); | ||
expect( | ||
screen.getByRole("link", { name: "Sign in to Identity platform" }), | ||
).toBeInTheDocument(); | ||
}); |
Oops, something went wrong.