mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2025-12-19 05:37:43 +00:00
apply review comments
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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> </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) => (
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user