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
-2
View File
@@ -44,7 +44,6 @@
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"jambonz-ui": "^0.0.19", "jambonz-ui": "^0.0.19",
"react": "^18.0.0", "react": "^18.0.0",
"react-blockies": "^1.4.1",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-feather": "^2.0.10", "react-feather": "^2.0.10",
"react-router-dom": "^6.3.0" "react-router-dom": "^6.3.0"
@@ -54,7 +53,6 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/node": "^18.6.1", "@types/node": "^18.6.1",
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@types/react-blockies": "^1.4.1",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6", "@typescript-eslint/parser": "^5.30.6",
-2
View File
@@ -1,5 +1,4 @@
import type { Vendor } from "src/vendor/types"; import type { Vendor } from "src/vendor/types";
import type { Scope } from "src/store/types";
/** Simple types */ /** Simple types */
@@ -117,7 +116,6 @@ export interface User {
service_provider_sid?: string | null; service_provider_sid?: string | null;
initial_password?: string; initial_password?: string;
permissions?: UserPermissions; permissions?: UserPermissions;
enumScope: Scope;
} }
export interface UserLogin extends User { export interface UserLogin extends User {
+9 -11
View File
@@ -1,21 +1,19 @@
import React from "react"; import React from "react";
import { H1, H2 } from "jambonz-ui"; import { H1 } from "jambonz-ui";
import { TestProvider } from "src/test"; import { TestProvider } from "src/test";
import { ScopedAccess } from "./scoped-access"; import { ScopedAccess } from "./scoped-access";
import type { ScopedAccessProps } from "./scoped-access"; import type { ScopedAccessProps } from "./scoped-access";
import { Scope } from "src/store/types";
/** Wrapper to pass different user scopes as enum values */ /** Wrapper to pass different user scopes as enum values */
const ScopedAccessTestWrapper = (props: Partial<ScopedAccessProps>) => { const ScopedAccessTestWrapper = (props: Partial<ScopedAccessProps>) => {
return ( return (
<TestProvider> <TestProvider>
<ScopedAccess <ScopedAccess scope={props.scope!}>
enumScope={props.enumScope!}
noAccessRender={<H2>{"noAccessRender"}</H2>}
>
<div className="scope-div"> <div className="scope-div">
<H1>ScopedAccess: {props.enumScope}</H1> <H1>ScopedAccess: {props.scope}</H1>
</div> </div>
</ScopedAccess> </ScopedAccess>
</TestProvider> </TestProvider>
@@ -24,20 +22,20 @@ const ScopedAccessTestWrapper = (props: Partial<ScopedAccessProps>) => {
describe("<ScopedAccess>", () => { describe("<ScopedAccess>", () => {
it("has sufficient scope - admin", () => { it("has sufficient scope - admin", () => {
cy.mount(<ScopedAccessTestWrapper enumScope={0} />); cy.mount(<ScopedAccessTestWrapper scope={Scope.admin} />);
cy.get(".scope-div").should("exist"); cy.get(".scope-div").should("exist");
cy.get("h1").should("exist"); cy.get("h1").should("exist");
}); });
it("has insufficient scope - service_provider", () => { 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(".scope-div").should("not.exist");
cy.get("h2").should("exist"); cy.get("h1").should("not.exist");
}); });
it("has insufficient scope - account", () => { 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(".scope-div").should("not.exist");
cy.get("h2").should("exist"); cy.get("h1").should("not.exist");
}); });
}); });
+3 -12
View File
@@ -5,25 +5,16 @@ import { useSelectState } from "src/store";
import type { Scope } from "src/store/types"; import type { Scope } from "src/store/types";
export type ScopedAccessProps = { export type ScopedAccessProps = {
enumScope: Scope; scope: Scope;
children: React.ReactNode; children: React.ReactNode;
noAccessRender?: React.ReactNode;
}; };
export const ScopedAccess = ({ export const ScopedAccess = ({ scope, children }: ScopedAccessProps) => {
enumScope,
children,
noAccessRender,
}: ScopedAccessProps) => {
const user = useSelectState("user"); const user = useSelectState("user");
if (user && user.enumScope <= enumScope) { if (user && user.scopeAccess >= scope) {
return <>{children}</>; return <>{children}</>;
} }
if (user && user.enumScope >= enumScope && noAccessRender) {
return <>{noAccessRender}</>;
}
return null; return null;
}; };
+5 -26
View File
@@ -1,9 +1,8 @@
import React, { useEffect, useState, useMemo } from "react"; import React, { useEffect, useState, useMemo } from "react";
import Blockies from "react-blockies"; import { classNames, M, Icon, Button } from "jambonz-ui";
import { classNames, getCssVar, M, Icon, Button } from "jambonz-ui";
import { Link, useLocation } from "react-router-dom"; 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 { naviTop, naviByo } from "./items";
import { import {
useSelectState, useSelectState,
@@ -17,6 +16,7 @@ import type { NaviItem } from "./items";
import "./styles.scss"; import "./styles.scss";
import { ScopedAccess } from "src/components/scoped-access"; import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types";
type CommonProps = { type CommonProps = {
handleMenu: () => void; handleMenu: () => void;
@@ -57,7 +57,6 @@ export const Navi = ({
handleLogout, handleLogout,
}: NaviProps) => { }: NaviProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const user = useSelectState("user");
const accessControl = useSelectState("accessControl"); const accessControl = useSelectState("accessControl");
const serviceProviders = useSelectState("serviceProviders"); const serviceProviders = useSelectState("serviceProviders");
const currentServiceProvider = useSelectState("currentServiceProvider"); const currentServiceProvider = useSelectState("currentServiceProvider");
@@ -150,7 +149,7 @@ export const Navi = ({
); );
}) })
) : ( ) : (
<option>currentServiceProvider.name</option> <option>&nbsp;</option>
)} )}
</select> </select>
<span> <span>
@@ -159,7 +158,7 @@ export const Navi = ({
</span> </span>
</div> </div>
</div> </div>
<ScopedAccess enumScope={0}> <ScopedAccess scope={Scope.admin}>
<button <button
type="button" type="button"
onClick={() => setModal(true)} onClick={() => setModal(true)}
@@ -170,26 +169,6 @@ export const Navi = ({
</button> </button>
</ScopedAccess> </ScopedAccess>
</div> </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"> <div className="navi__routes">
<ul> <ul>
{naviTop.map((item) => ( {naviTop.map((item) => (
+5 -4
View File
@@ -2,9 +2,9 @@ import { getServiceProviders } from "src/api";
import { sortLocaleName } from "src/utils"; import { sortLocaleName } from "src/utils";
import { getToken, parseJwt } from "src/router/auth"; 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 { 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 */ /** A generic action assumes action.type is ALWAYS our state key */
/** Since this is how we're designing our state interface we cool */ /** Since this is how we're designing our state interface we cool */
@@ -62,10 +62,11 @@ export const currentServiceProviderAction = (
return genericAction(state, action); return genericAction(state, action);
}; };
export const userAsyncAction = async (): Promise<User> => { export const userAsyncAction = async (): Promise<ScopeAccess> => {
const token = getToken(); const token = getToken();
const userData = parseJwt(token); const userData = parseJwt(token);
return { ...userData, enumScope: Scope[userData.scope] };
return { ...userData, scopeAccess: Scope[userData.scope] };
}; };
export const serviceProvidersAsyncAction = async (): Promise< export const serviceProvidersAsyncAction = async (): Promise<
+1 -1
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 */ /** This will let us make a hook so dispatch is accessible anywhere */
let globalDispatch: GlobalDispatch; let globalDispatch: GlobalDispatch;
+25 -8
View File
@@ -1,6 +1,10 @@
import React from "react"; 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; export type IMessage = string | JSX.Element;
@@ -14,19 +18,32 @@ export interface ACL {
hasMSTeamsFqdn: boolean; hasMSTeamsFqdn: boolean;
} }
export enum Scope {
"admin" = 0,
"service_provider" = 1,
"account" = 2,
}
export interface FeatureFlag { export interface FeatureFlag {
development: boolean; 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 { export interface State {
/** logged in user */ /** logged in user */
user?: User; user?: ScopeAccess;
/** global toast notifications */ /** global toast notifications */
toast?: Toast; toast?: Toast;
/** feature flags from vite ENV */ /** feature flags from vite ENV */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"types": ["@types/react", "@types/react-dom", "@types/react-blockies"], "types": ["@types/react", "@types/react-dom"],
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],