diff --git a/src/api/types.ts b/src/api/types.ts index 4fceff4..4d03f52 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,4 +1,5 @@ import type { Vendor } from "src/vendor/types"; +import type { Scope } from "src/store/types"; /** Simple types */ @@ -116,6 +117,7 @@ export interface User { service_provider_sid?: string | null; initial_password?: string; permissions?: UserPermissions; + enumScope: Scope; } export interface UserLogin extends User { diff --git a/src/components/scoped-access.cy.tsx b/src/components/scoped-access.cy.tsx new file mode 100644 index 0000000..6697b4a --- /dev/null +++ b/src/components/scoped-access.cy.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { H1, H2 } from "jambonz-ui"; + +import { TestProvider } from "src/test"; +import { ScopedAccess } from "./scoped-access"; + +import type { ScopedAccessProps } from "./scoped-access"; + +/** Wrapper to pass different user scopes as enum values */ +const ScopedAccessTestWrapper = (props: Partial) => { + return ( + + {"noAccessRender"}} + > +
+

ScopedAccess: {props.enumScope}

+
+
+
+ ); +}; + +describe("", () => { + it("has sufficient scope - admin", () => { + cy.mount(); + cy.get(".scope-div").should("exist"); + cy.get("h1").should("exist"); + }); + + it("has insufficient scope - service_provider", () => { + cy.mount(); + cy.get(".scope-div").should("not.exist"); + cy.get("h2").should("exist"); + }); + + it("has insufficient scope - account", () => { + cy.mount(); + cy.get(".scope-div").should("not.exist"); + cy.get("h2").should("exist"); + }); +}); diff --git a/src/components/scoped-access.tsx b/src/components/scoped-access.tsx new file mode 100644 index 0000000..7e90b20 --- /dev/null +++ b/src/components/scoped-access.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +import { useSelectState } from "src/store"; + +import type { Scope } from "src/store/types"; + +export type ScopedAccessProps = { + enumScope: Scope; + children: React.ReactNode; + noAccessRender?: React.ReactNode; +}; + +export const ScopedAccess = ({ + enumScope, + children, + noAccessRender, +}: ScopedAccessProps) => { + const user = useSelectState("user"); + + if (user && user.enumScope <= enumScope) { + return <>{children}; + } + + if (user && user.enumScope >= enumScope && noAccessRender) { + return <>{noAccessRender}; + } + + return null; +}; diff --git a/src/containers/internal/navi/index.tsx b/src/containers/internal/navi/index.tsx index 140ef4a..8fc3871 100644 --- a/src/containers/internal/navi/index.tsx +++ b/src/containers/internal/navi/index.tsx @@ -16,6 +16,7 @@ import { postServiceProviders } from "src/api"; import type { NaviItem } from "./items"; import "./styles.scss"; +import { ScopedAccess } from "src/components/scoped-access"; type CommonProps = { handleMenu: () => void; @@ -149,7 +150,7 @@ export const Navi = ({ ); }) ) : ( - + )} @@ -158,14 +159,16 @@ export const Navi = ({ - + + + {/* Intentionally hiding this as we will need to work this out as we go... */} {/* ACL component will need to be updated for new user scope/permissions handling */} diff --git a/src/store/actions.ts b/src/store/actions.ts index 9f83061..a150152 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -3,7 +3,8 @@ import { sortLocaleName } from "src/utils"; import { getToken, parseJwt } from "src/router/auth"; import type { State, Action } from "./types"; -import type { ServiceProvider, User } from "src/api/types"; +import { Scope } from "./types"; +import { ServiceProvider, User } from "src/api/types"; /** A generic action assumes action.type is ALWAYS our state key */ /** Since this is how we're designing our state interface we cool */ @@ -63,7 +64,8 @@ export const currentServiceProviderAction = ( export const userAsyncAction = async (): Promise => { const token = getToken(); - return parseJwt(token); + const userData = parseJwt(token); + return { ...userData, enumScope: Scope[userData.scope] }; }; export const serviceProvidersAsyncAction = async (): Promise< diff --git a/src/store/types.ts b/src/store/types.ts index f6768bb..c3772f8 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -14,6 +14,12 @@ export interface ACL { hasMSTeamsFqdn: boolean; } +export enum Scope { + "admin" = 0, + "service_provider" = 1, + "account" = 2, +} + export interface FeatureFlag { development: boolean; }