Skip to content

Commit

Permalink
[CLIENT] Add Authorization (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
yousinix authored Sep 28, 2020
1 parent f2d23ff commit 1c8073f
Show file tree
Hide file tree
Showing 22 changed files with 276 additions and 122 deletions.
162 changes: 88 additions & 74 deletions client/components/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import { StatefulMenu } from "baseui/menu";
import { StatefulPopover, TRIGGER_TYPE } from "baseui/popover";
import {
Expand All @@ -12,15 +12,16 @@ import { Button } from "baseui/button";
import { useRouter } from "next/dist/client/router";
import { Plus } from "baseui/icon";

import { useMeQuery, useLogoutMutation } from "../generated/graphql";
import { useLogoutMutation, UserRole as R } from "../generated/graphql";
import { Block } from "baseui/block";
import { useAuth } from "../hooks/useAuth";

interface NavBarProps {}

export const NavBar: React.FC<NavBarProps> = () => {
const [{ data }] = useMeQuery();
const [, logout] = useLogoutMutation();
const router = useRouter();
const { role } = useAuth();
const [, logout] = useLogoutMutation();

return (
<HeaderNavigation>
Expand All @@ -32,58 +33,63 @@ export const NavBar: React.FC<NavBarProps> = () => {
</StyledNavigationList>

<StyledNavigationList $align={ALIGN.left}>
<StyledNavigationItem>
<StatefulPopover
triggerType={TRIGGER_TYPE.hover}
content={() => (
<StatefulMenu
onItemSelect={({ item }) => router.push(item.route)}
items={{
__ungrouped: [],
Requests: [
{
label: "Resource Request",
route: "/view/resourceRequests",
},
{
label: "Release Request",
route: "/view/releaseRequests",
},
],
}}
/>
)}
>
<Label1>Talents</Label1>
</StatefulPopover>
</StyledNavigationItem>
<StyledNavigationItem>
<StatefulPopover
triggerType={TRIGGER_TYPE.hover}
content={() => (
<StatefulMenu
onItemSelect={({ item }) => router.push(item.route)}
items={{
__ungrouped: [],
Skills: [
{
label: "Skills",
route: "/view/skills",
},
],
Certifications: [
{
label: "Certificate Providers",
route: "/view/certificateProviders",
},
],
}}
/>
)}
>
<Label1>Skills & Certifications</Label1>
</StatefulPopover>
</StyledNavigationItem>
{role == R.Admin || role == R.Manager ? (
<StyledNavigationItem>
<StatefulPopover
triggerType={TRIGGER_TYPE.hover}
content={() => (
<StatefulMenu
onItemSelect={({ item }) => router.push(item.route)}
items={{
__ungrouped: [],
Requests: [
{
label: "Resource Request",
route: "/view/resourceRequests",
},
{
label: "Release Request",
route: "/view/releaseRequests",
},
],
}}
/>
)}
>
<Label1>Talents</Label1>
</StatefulPopover>
</StyledNavigationItem>
) : null}

{role == R.Admin ? (
<StyledNavigationItem>
<StatefulPopover
triggerType={TRIGGER_TYPE.hover}
content={() => (
<StatefulMenu
onItemSelect={({ item }) => router.push(item.route)}
items={{
__ungrouped: [],
Skills: [
{
label: "Skills",
route: "/view/skills",
},
],
Certifications: [
{
label: "Certificate Providers",
route: "/view/certificateProviders",
},
],
}}
/>
)}
>
<Label1>Skills & Certifications</Label1>
</StatefulPopover>
</StyledNavigationItem>
) : null}
</StyledNavigationList>
<StyledNavigationList $align={ALIGN.center} />

Expand All @@ -96,23 +102,31 @@ export const NavBar: React.FC<NavBarProps> = () => {
onItemSelect={({ item }) => router.push(item.route)}
items={{
__ungrouped: [],
Requests: [
{
label: "Resource Request",
route: "/create/resourceRequest",
},
{
label: "Release Request",
route: "/create/releaseRequest",
},
],
"Skills & Certificates": [
{ label: "Skill", route: "/create/skill" },
{
label: "Certificate Provider",
route: "/create/certificateProvider",
},
],
...(role == R.Admin || role == R.Manager
? {
Requests: [
{
label: "Resource Request",
route: "/create/resourceRequest",
},
{
label: "Release Request",
route: "/create/releaseRequest",
},
],
}
: {}),
...(role == R.Admin
? {
Lists: [
{ label: "Skill", route: "/create/skill" },
{
label: "Certificate Provider",
route: "/create/certificateProvider",
},
],
}
: {}),
}}
/>
)}
Expand All @@ -121,7 +135,7 @@ export const NavBar: React.FC<NavBarProps> = () => {
</StatefulPopover>
</StyledNavigationItem>
<StyledNavigationItem>
{!data?.me ? (
{!role ? (
<Button onClick={() => router.push("/login")}>Login</Button>
) : (
<Button
Expand Down
18 changes: 18 additions & 0 deletions client/components/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { AuthContext } from "../../context/AuthContext";
import { useRoleQuery } from "../../generated/graphql";

export const AuthProvider: React.FC = ({ children }) => {
const [{ data, fetching }] = useRoleQuery();

return (
<AuthContext.Provider
value={{
isLoading: fetching,
role: data?.role || null,
}}
>
{children}
</AuthContext.Provider>
);
};
12 changes: 12 additions & 0 deletions client/context/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import { UserRole } from "../generated/graphql";

export interface IAuthContext {
isLoading: boolean;
role: UserRole | null;
}

export const AuthContext = React.createContext<IAuthContext>({
isLoading: true,
role: null,
});
3 changes: 3 additions & 0 deletions client/graphql/queries/role.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query Role {
role
}
48 changes: 48 additions & 0 deletions client/hocs/withAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Block } from "baseui/block";
import { useRouter } from "next/dist/client/router";
import { Loading } from "../components/Loading";
import { UserRole } from "../generated/graphql";
import { useAuth } from "../hooks/useAuth";

/**
* A Higher Order Component to protect pages from unauthenticated and
* unauthorized usage.
* Redirects to "/login" if unauthenticated.
* Redirects to "/" if unauthorized.
*
* @param roles the roles authorized to access the page,
* empty array or undefined if all.
*/
export const withAuth = (
WrappedComponent: React.ComponentType,
roles?: UserRole[]
) => {
const WithAuthWrapper = (props: JSX.IntrinsicAttributes) => {
const { isLoading, role } = useAuth();
const router = useRouter();

if (isLoading) {
return (
<Block display="flex" height="100vh" width="100vw">
<Loading />
</Block>
);
}

if (!role) {
// unauthenticated
router.push("/login");
return <></>;
}

if (roles && roles.length > 0 && !roles.includes(role)) {
// unauthorized
router.push("/");
return <></>;
}

return <WrappedComponent {...props} />;
};

return WithAuthWrapper;
};
10 changes: 10 additions & 0 deletions client/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import { AuthContext, IAuthContext } from "../context/AuthContext";

export function useAuth(): IAuthContext {
const context = React.useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
17 changes: 12 additions & 5 deletions client/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@ import "../styles/global.css";
import { ItworxTheme } from "../styles/theme";
import { debug, styletron } from "../styletron";
import { urqlClient } from "../urql/urqlClient";
import { AuthProvider } from "../components/providers/AuthProvider";


export default class MainApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<UrqlProvider value={urqlClient}>
<StyletronProvider value={styletron} debug={debug} debugAfterHydration>
<BaseProvider theme={ItworxTheme}>
<Component {...pageProps} />
</BaseProvider>
</StyletronProvider>
<AuthProvider>
<StyletronProvider
value={styletron}
debug={debug}
debugAfterHydration
>
<BaseProvider theme={ItworxTheme}>
<Component {...pageProps} />
</BaseProvider>
</StyletronProvider>
</AuthProvider>
</UrqlProvider>
);
}
Expand Down
9 changes: 6 additions & 3 deletions client/pages/create/certificateProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { useRouter } from "next/dist/client/router";

import { CertificateProviderForm } from "../../components/CertificateProviderForm";
import { MainLayout } from "../../components/MainLayout";
import { useCreateCertificationProviderMutation } from "../../generated/graphql";

import {
useCreateCertificationProviderMutation,
UserRole,
} from "../../generated/graphql";
import { withAuth } from "../../hocs/withAuth";

const CreateCertificationProvider: React.FC<{}> = () => {
const router = useRouter();
Expand All @@ -30,4 +33,4 @@ const CreateCertificationProvider: React.FC<{}> = () => {
);
};

export default CreateCertificationProvider;
export default withAuth(CreateCertificationProvider, [UserRole.Admin]);
8 changes: 6 additions & 2 deletions client/pages/create/releaseRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { ReleaseRequestForm } from "../../components/ReleaseRequestForm";
import {
ReleaseRequestInput,
useCreateReleaseRequestMutation,
UserRole,
} from "../../generated/graphql";
import { useRouter } from "next/dist/client/router";
import { toErrorMap } from "../../utils/toErrorMap";

import { withAuth } from "../../hocs/withAuth";

const CreateReleaseRequest: React.FC<{}> = () => {
const [, createReleaseRequest] = useCreateReleaseRequestMutation();
Expand Down Expand Up @@ -46,4 +47,7 @@ const CreateReleaseRequest: React.FC<{}> = () => {
);
};

export default CreateReleaseRequest;
export default withAuth(CreateReleaseRequest, [
UserRole.Admin,
UserRole.Manager,
]);
11 changes: 7 additions & 4 deletions client/pages/create/resourceRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from "react";

import { useRouter } from "next/dist/client/router";
import { ResourceRequestForm } from "../../components/ResourceRequestForm";
import {
ResourceRequestInput,
useCreateResourceRequestMutation,
UserRole,
} from "../../generated/graphql";
import { useRouter } from "next/dist/client/router";

import { withAuth } from "../../hocs/withAuth";

const CreateResourceRequest: React.FC<{}> = () => {
const [, createResourceRequest] = useCreateResourceRequestMutation();
Expand Down Expand Up @@ -49,4 +49,7 @@ const CreateResourceRequest: React.FC<{}> = () => {
);
};

export default CreateResourceRequest;
export default withAuth(CreateResourceRequest, [
UserRole.Admin,
UserRole.Manager,
]);
Loading

0 comments on commit 1c8073f

Please sign in to comment.