apply review comments

This commit is contained in:
eglehelms
2022-11-25 17:28:58 +01:00
parent 1666703c13
commit 2ad285faed
9 changed files with 49 additions and 67 deletions

View File

@@ -44,7 +44,6 @@
"dayjs": "^1.11.5",
"jambonz-ui": "^0.0.19",
"react": "^18.0.0",
"react-blockies": "^1.4.1",
"react-dom": "^18.0.0",
"react-feather": "^2.0.10",
"react-router-dom": "^6.3.0"
@@ -54,7 +53,6 @@
"@types/express": "^4.17.13",
"@types/node": "^18.6.1",
"@types/react": "^18.0.0",
"@types/react-blockies": "^1.4.1",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",

View File

@@ -1,5 +1,4 @@
import type { Vendor } from "src/vendor/types";
import type { Scope } from "src/store/types";
/** Simple types */
@@ -117,7 +116,6 @@ export interface User {
service_provider_sid?: string | null;
initial_password?: string;
permissions?: UserPermissions;
enumScope: Scope;
}
export interface UserLogin extends User {

View File

@@ -1,21 +1,19 @@
import React from "react";
import { H1, H2 } from "jambonz-ui";
import { H1 } from "jambonz-ui";
import { TestProvider } from "src/test";
import { ScopedAccess } from "./scoped-access";
import type { ScopedAccessProps } from "./scoped-access";
import { Scope } from "src/store/types";
/** Wrapper to pass different user scopes as enum values */
const ScopedAccessTestWrapper = (props: Partial<ScopedAccessProps>) => {
return (
<TestProvider>
<ScopedAccess
enumScope={props.enumScope!}
noAccessRender={<H2>{"noAccessRender"}</H2>}
>
<ScopedAccess scope={props.scope!}>
<div className="scope-div">
<H1>ScopedAccess: {props.enumScope}</H1>
<H1>ScopedAccess: {props.scope}</H1>
</div>
</ScopedAccess>
</TestProvider>
@@ -24,20 +22,20 @@ const ScopedAccessTestWrapper = (props: Partial<ScopedAccessProps>) => {
describe("<ScopedAccess>", () => {
it("has sufficient scope - admin", () => {
cy.mount(<ScopedAccessTestWrapper enumScope={0} />);
cy.mount(<ScopedAccessTestWrapper scope={Scope.admin} />);
cy.get(".scope-div").should("exist");
cy.get("h1").should("exist");
});
it("has insufficient scope - service_provider", () => {
cy.mount(<ScopedAccessTestWrapper enumScope={1} />);
cy.mount(<ScopedAccessTestWrapper scope={Scope.service_provider} />);
cy.get(".scope-div").should("not.exist");
cy.get("h2").should("exist");
cy.get("h1").should("not.exist");
});
it("has insufficient scope - account", () => {
cy.mount(<ScopedAccessTestWrapper enumScope={2} />);
cy.mount(<ScopedAccessTestWrapper scope={Scope.account} />);
cy.get(".scope-div").should("not.exist");
cy.get("h2").should("exist");
cy.get("h1").should("not.exist");
});
});

View File

@@ -5,25 +5,16 @@ import { useSelectState } from "src/store";
import type { Scope } from "src/store/types";
export type ScopedAccessProps = {
enumScope: Scope;
scope: Scope;
children: React.ReactNode;
noAccessRender?: React.ReactNode;
};
export const ScopedAccess = ({
enumScope,
children,
noAccessRender,
}: ScopedAccessProps) => {
export const ScopedAccess = ({ scope, children }: ScopedAccessProps) => {
const user = useSelectState("user");
if (user && user.enumScope <= enumScope) {
if (user && user.scopeAccess >= scope) {
return <>{children}</>;
}
if (user && user.enumScope >= enumScope && noAccessRender) {
return <>{noAccessRender}</>;
}
return null;
};

View File

@@ -1,9 +1,8 @@
import React, { useEffect, useState, useMemo } from "react";
import Blockies from "react-blockies";
import { classNames, getCssVar, M, Icon, Button } from "jambonz-ui";
import { classNames, M, Icon, Button } from "jambonz-ui";
import { Link, useLocation } from "react-router-dom";
import { Icons, ModalForm, AccessControl } from "src/components";
import { Icons, ModalForm } from "src/components";
import { naviTop, naviByo } from "./items";
import {
useSelectState,
@@ -17,6 +16,7 @@ import type { NaviItem } from "./items";
import "./styles.scss";
import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types";
type CommonProps = {
handleMenu: () => void;
@@ -57,7 +57,6 @@ export const Navi = ({
handleLogout,
}: NaviProps) => {
const dispatch = useDispatch();
const user = useSelectState("user");
const accessControl = useSelectState("accessControl");
const serviceProviders = useSelectState("serviceProviders");
const currentServiceProvider = useSelectState("currentServiceProvider");
@@ -150,7 +149,7 @@ export const Navi = ({
);
})
) : (
<option>currentServiceProvider.name</option>
<option>&nbsp;</option>
)}
</select>
<span>
@@ -159,7 +158,7 @@ export const Navi = ({
</span>
</div>
</div>
<ScopedAccess enumScope={0}>
<ScopedAccess scope={Scope.admin}>
<button
type="button"
onClick={() => setModal(true)}
@@ -170,26 +169,6 @@ export const Navi = ({
</button>
</ScopedAccess>
</div>
{/* 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 */}
{false && user && (
<div className="navi__user">
<Blockies
seed={user!.user_sid}
size={6}
scale={6}
color={getCssVar("--jambonz")}
bgColor={getCssVar("--pink")}
spotColor={getCssVar("--teal")}
className="avatar"
/>
<AccessControl acl="hasAdminAuth">
<button type="button" className="btnty adduser" title="Add user">
<Icons.PlusCircle />
</button>
</AccessControl>
</div>
)}
<div className="navi__routes">
<ul>
{naviTop.map((item) => (

View File

@@ -2,9 +2,9 @@ import { getServiceProviders } from "src/api";
import { sortLocaleName } from "src/utils";
import { getToken, parseJwt } from "src/router/auth";
import type { State, Action } from "./types";
import type { State, Action, ScopeAccess } from "./types";
import { Scope } from "./types";
import { ServiceProvider, User } from "src/api/types";
import { ServiceProvider } 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 */
@@ -62,10 +62,11 @@ export const currentServiceProviderAction = (
return genericAction(state, action);
};
export const userAsyncAction = async (): Promise<User> => {
export const userAsyncAction = async (): Promise<ScopeAccess> => {
const token = getToken();
const userData = parseJwt(token);
return { ...userData, enumScope: Scope[userData.scope] };
return { ...userData, scopeAccess: Scope[userData.scope] };
};
export const serviceProvidersAsyncAction = async (): Promise<

View File

@@ -77,7 +77,7 @@ const middleware: MiddleWare = (dispatch) => {
};
};
const StateContext = React.createContext<AppStateContext>(null!);
export const StateContext = React.createContext<AppStateContext>(null!);
/** This will let us make a hook so dispatch is accessible anywhere */
let globalDispatch: GlobalDispatch;

View File

@@ -1,6 +1,10 @@
import React from "react";
import type { User, ServiceProvider } from "src/api/types";
import type {
ServiceProvider,
UserScopes,
UserPermissions,
} from "src/api/types";
export type IMessage = string | JSX.Element;
@@ -14,19 +18,32 @@ export interface ACL {
hasMSTeamsFqdn: boolean;
}
export enum Scope {
"admin" = 0,
"service_provider" = 1,
"account" = 2,
}
export interface FeatureFlag {
development: boolean;
}
export enum Scope {
"admin" = 2,
"service_provider" = 1,
"account" = 0,
}
export interface UserJWT {
scope: UserScopes;
user_sid: string;
force_change: boolean;
account_sid?: string | null;
service_provider_sid?: string | null;
permissions?: UserPermissions;
}
export interface ScopeAccess extends UserJWT {
scopeAccess: Scope;
}
export interface State {
/** logged in user */
user?: User;
user?: ScopeAccess;
/** global toast notifications */
toast?: Toast;
/** feature flags from vite ENV */

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"types": ["@types/react", "@types/react-dom", "@types/react-blockies"],
"types": ["@types/react", "@types/react-dom"],
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],