Skip to content

Commit

Permalink
Auth logout (#1492)
Browse files Browse the repository at this point in the history
* WG309 Auth Context and Welcome screen - WIP

* WG309 Auth Context and Welcome screen - WIP2

* WG309 Auth Context and Welcome screen - WIP

* Add sign_in handler

* WG309 Update welcome screen form

* WG309 Update sign in formData submit

* WG309 Update sign in req and payload

* Fix format in helm testdata

* Enable CORS for dev

* Remove unnecessary package

* WG309 Userinfo draft and cleanup

* Callback working - use REACT_API_URL=http://0.0.0.0:9001

* WG309 User info returns 200

* WG309 Remove CORS related code

* WG309 Remove CORS related code - update

* Add tests for Signin handler

* Update tests

* Remove username from sign in form

* Add token signer/verifier

* Fix test

* Add new middleware

* Add tests for Signin handler

* User from stash

* WG309 AuthContext update on user info check

* WG309 AuthContext update on user info check - 2

* WG309 Refactor loading in AuthContext

* WG309 AuthContext reruns on history change

* Fix conflict

* WG309 AuthContext refactor

* WG309 On refresh page doesnt go to 404 anymore

* Wrap loading page

* WG309 Fix oidc return url

* Issued cookies should have the Secure attribute to true

* WG309 Display alert error

* Remove secret yaml example

* WG309 Improve loading transition

* Add OIDC flow test for user info endpoint

* WG309 Improve loading transition - 2

* Fix eslint errors

* Split out authchecking from the authcontext, single router

* WG309 Add switch for password visibility

* WG309 Add switch for password visibility - updated

* Add BE logout code

* Update package.json with main version

* Lint it

* WG407 Add user settings section - WIP

* Rm security risk printing user-supplied value

* WG407 Add user settings section - WIP2

* WG309 Hide UI behind flag - WIP

* Push first pass at GET /v1/config

* https in tests

* Revert "https in tests"

This reverts commit 286211b.

* get feature flags innit

* WG309 Hide UI behind feature flag - updated

* Linting and testing

* Update exports

* untagglin

* fix fix fix

* OIDC is optional now

* Update package.lock

* WG407 Hide userSettings when authFlag is null

* WG309 Hide UI behind feature flag - updated2

* WG407 Hide userSettings when authFlag is null - 2

* Fix issues in package-lock.json

* Fix issues in package-lock.json - 2

* WG407 Add FeatureFlags context

* WG407 Add FeatureFlags context - updated

* WG407 Add FeatureFlags context - updated2

* Fix linting error

* Update ui/contexts/AuthContext.tsx

Co-authored-by: Simon <footless@gmail.com>

* Implement PR feedback

* Implement PR feedback - 2

Co-authored-by: Yiannis <yiannis@weave.works>
Co-authored-by: Simon Howe <footless@gmail.com>
  • Loading branch information
3 people committed Feb 24, 2022
1 parent 2d686a9 commit 7297ddf
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 44 deletions.
27 changes: 3 additions & 24 deletions ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ErrorBoundary from "./components/ErrorBoundary";
import Layout from "./components/Layout";
import AppContextProvider from "./contexts/AppContext";
import AuthContextProvider, { AuthCheck } from "./contexts/AuthContext";
import FeatureFlagsContextProvider from "./contexts/FeatureFlags";
import {
Applications as appsClient,
GitProvider,
Expand Down Expand Up @@ -86,33 +87,13 @@ const App = () => (
);

export default function AppContainer() {
const [authFlag, setAuthFlag] = React.useState<boolean>(null);

const getAuthFlag = React.useCallback(() => {
fetch("/v1/featureflags")
.then((response) => response.json())
.then((data) =>
setAuthFlag(data.flags.WEAVE_GITOPS_AUTH_ENABLED === "true")
)
.catch((err) => console.log(err));
}, []);

React.useEffect(() => {
getAuthFlag();
}, [getAuthFlag]);

// Loading...
if (authFlag === null) {
return null;
}

return (
<MuiThemeProvider theme={muiTheme}>
<ThemeProvider theme={theme}>
<Fonts />
<GlobalStyle />
<Router>
{authFlag ? (
<FeatureFlagsContextProvider>
<AuthContextProvider>
<Switch>
{/* <Signin> does not use the base page <Layout> so pull it up here */}
Expand All @@ -125,9 +106,7 @@ export default function AppContainer() {
</Route>
</Switch>
</AuthContextProvider>
) : (
<App />
)}
</FeatureFlagsContextProvider>
</Router>
</ThemeProvider>
</MuiThemeProvider>
Expand Down
5 changes: 5 additions & 0 deletions ui/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import RemoveCircleIcon from "@material-ui/icons/RemoveCircle";
import SaveAltIcon from "@material-ui/icons/SaveAlt";
import SkipNextIcon from "@material-ui/icons/SkipNext";
import SkipPreviousIcon from "@material-ui/icons/SkipPrevious";
import LogoutIcon from "@material-ui/icons/ExitToApp";
import * as React from "react";
import styled from "styled-components";
import { colors, spacing } from "../typedefs/styled";
Expand All @@ -34,6 +35,7 @@ export enum IconType {
SkipNextIcon,
SkipPreviousIcon,
RemoveCircleIcon,
LogoutIcon,
}

type Props = {
Expand Down Expand Up @@ -91,6 +93,9 @@ function getIcon(i: IconType) {
case IconType.RemoveCircleIcon:
return RemoveCircleIcon;

case IconType.LogoutIcon:
return LogoutIcon;

default:
break;
}
Expand Down
10 changes: 4 additions & 6 deletions ui/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import styled from "styled-components";
import useNavigation from "../hooks/navigation";
import { PageRoute } from "../lib/types";
import { formatURL, getNavValue } from "../lib/utils";
import { FeatureFlags } from "../contexts/FeatureFlags";
import Flex from "./Flex";
import Link from "./Link";
import Logo from "./Logo";
import UserSettings from "./UserSettings";

type Props = {
className?: string;
Expand Down Expand Up @@ -94,20 +96,16 @@ const TopToolBar = styled(Flex)`
}
`;

//style for account icon - disabled while no account functionality exists
// const UserAvatar = styled(Icon)`
// padding-right: ${(props) => props.theme.spacing.medium};
// `;

function Layout({ className, children }: Props) {
const { authFlag } = React.useContext(FeatureFlags);
const { currentPage } = useNavigation();

return (
<div className={className}>
<AppContainer>
<TopToolBar between align>
<Logo />
{/* code for account icon - disabled while no account functionality exists <UserAvatar size="xl" type={IconType.Account} color="white" /> */}
{authFlag ? <UserSettings /> : null}
</TopToolBar>
<Main wide>
<NavContainer>
Expand Down
68 changes: 68 additions & 0 deletions ui/components/UserSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
IconButton,
ListItemIcon,
Menu,
MenuItem,
Tooltip,
} from "@material-ui/core";
import * as React from "react";
import styled from "styled-components";
import { Auth } from "../contexts/AuthContext";
import Icon, { IconType } from "./Icon";

const UserAvatar = styled(Icon)`
padding-right: ${(props) => props.theme.spacing.medium};
`;

const SettingsMenu = styled(Menu)`
.MuiListItemIcon-root {
min-width: 25px;
color: ${(props) => props.theme.colors.black};
}
`;

function UserSettings() {
const [anchorEl, setAnchorEl] = React.useState(null);
const { userInfo, logOut } = React.useContext(Auth);

const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};

return (
<>
<Tooltip title="Account settings">
<IconButton
onClick={handleClick}
aria-controls={open ? "account-menu" : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
>
<UserAvatar size="xl" type={IconType.Account} color="white" />
</IconButton>
</Tooltip>
<SettingsMenu
anchorEl={anchorEl}
id="account-menu"
open={open}
onClose={handleClose}
onClick={handleClose}
transformOrigin={{ horizontal: "right", vertical: "top" }}
>
<MenuItem>Hello, {userInfo?.email}</MenuItem>
<MenuItem onClick={() => logOut()}>
<ListItemIcon>
<Icon type={IconType.LogoutIcon} size="base" />
</ListItemIcon>
Logout
</MenuItem>
</SettingsMenu>
</>
);
}

export default styled(UserSettings)``;
65 changes: 51 additions & 14 deletions ui/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ import * as React from "react";
import { useHistory, Redirect } from "react-router-dom";
import Layout from "../components/Layout";
import LoadingPage from "../components/LoadingPage";
import { FeatureFlags } from "../contexts/FeatureFlags";

const USER_INFO = "/oauth2/userinfo";
const SIGN_IN = "/oauth2/sign_in";
const LOG_OUT = "/oauth2/logout";
const AUTH_PATH_SIGNIN = "/sign_in";

export const AuthCheck = ({ children }) => {
// If the auth flag is null go straight to rendering the children
const { authFlag } = React.useContext(FeatureFlags);

if (!authFlag) {
return children;
}

const { loading, userInfo } = React.useContext(Auth);

// Wait until userInfo is loaded before showing signin or app content
Expand Down Expand Up @@ -36,15 +45,18 @@ export type AuthContext = {
};
error: { status: number; statusText: string };
loading: boolean;
logOut: () => void;
};

export const Auth = React.createContext<AuthContext | null>(null);

export default function AuthContextProvider({ children }) {
const [userInfo, setUserInfo] = React.useState<{
email: string;
groups: string[];
}>(null);
const { authFlag } = React.useContext(FeatureFlags);
const [userInfo, setUserInfo] =
React.useState<{
email: string;
groups: string[];
}>(null);
const [loading, setLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState(null);
const history = useHistory();
Expand Down Expand Up @@ -80,21 +92,46 @@ export default function AuthContextProvider({ children }) {
.finally(() => setLoading(false));
}, []);

const logOut = React.useCallback(() => {
setLoading(true);
fetch(LOG_OUT, {
method: "POST",
})
.then((response) => {
if (response.status !== 200) {
setError(response);
return;
}
history.push("/sign_in");
})
.finally(() => setLoading(false));
}, []);

React.useEffect(() => {
if (!authFlag) {
return null;
}
getUserInfo();
return history.listen(getUserInfo);
}, [getUserInfo, history]);

return (
<Auth.Provider
value={{
signIn,
userInfo,
error,
loading,
}}
>
{children}
</Auth.Provider>
<>
{authFlag ? (
<Auth.Provider
value={{
signIn,
userInfo,
error,
loading,
logOut,
}}
>
{children}
</Auth.Provider>
) : (
children
)}
</>
);
}
40 changes: 40 additions & 0 deletions ui/contexts/FeatureFlags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from "react";

export type FeatureFlagsContext = {
authFlag: boolean | null;
};

export const FeatureFlags =
React.createContext<FeatureFlagsContext | null>(null);

export default function FeatureFlagsContextProvider({ children }) {
const [authFlag, setAuthFlag] = React.useState<boolean>(null);

const getAuthFlag = React.useCallback(() => {
fetch("/v1/featureflags")
.then((response) => response.json())
.then((data) =>
setAuthFlag(data.flags.WEAVE_GITOPS_AUTH_ENABLED === "true")
)
.catch((err) => console.log(err));
}, []);

React.useEffect(() => {
getAuthFlag();
}, [getAuthFlag]);

// Loading...
if (authFlag === null) {
return null;
}

return (
<FeatureFlags.Provider
value={{
authFlag,
}}
>
{children}
</FeatureFlags.Provider>
);
}
7 changes: 7 additions & 0 deletions ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import Footer from "./components/Footer";
import GithubDeviceAuthModal from "./components/GithubDeviceAuthModal";
import LoadingPage from "./components/LoadingPage";
import RepoInputWithAuth from "./components/RepoInputWithAuth";
import UserSettings from "./components/UserSettings";
import Icon, { IconType } from "./components/Icon";
import AppContextProvider from "./contexts/AppContext";
import AuthContextProvider from "./contexts/AuthContext";
import FeatureFlagsContextProvider, {
FeatureFlags,
} from "./contexts/FeatureFlags";
import CallbackStateContextProvider from "./contexts/CallbackStateContext";
import useApplications from "./hooks/applications";
import { Applications as applicationsClient } from "./lib/api/applications/applications.pb";
Expand All @@ -21,6 +25,8 @@ import Applications from "./pages/Applications";
import OAuthCallback from "./pages/OAuthCallback";

export {
FeatureFlagsContextProvider,
FeatureFlags,
AuthContextProvider,
AppContextProvider,
ApplicationAdd,
Expand All @@ -42,4 +48,5 @@ export {
Button,
Icon,
IconType,
UserSettings,
};

0 comments on commit 7297ddf

Please sign in to comment.