console: add time limits setting to security settings

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2420
GitOrigin-RevId: 9c2752c4939a8d22474277f6894bf50f02486899
This commit is contained in:
Ikechukwu Eze 2021-10-05 11:48:39 +01:00 committed by hasura-bot
parent 0a829f8762
commit 8ca962ab91
11 changed files with 92 additions and 11 deletions

View File

@ -32,6 +32,7 @@
- console: fix cross-schema array relationship suggestions
- console: add performance fixes for handle large db schemas
- console: fix missing cross-schema computed fields in permission builder
- console: add time limits setting to security settings
- cli: add support for `network` metadata object
- cli: `hasura migrate apply --all-databases` will return a non zero exit code if operation failed on atleast one database (#7499)
- cli: `migrate create --from-server` creates the migration and marks it as applied on the server

View File

@ -197,7 +197,7 @@ export function TableRow<T>({
: styles.justify_center
} ${singleColumn ? styles.single_column : styles.justify_center}`}
onClick={onClick(i)}
key={i}
key={`${index}${i}`}
>
{renderCol({
data,

View File

@ -15,23 +15,19 @@ const ApiLimitsComponent: React.FC<securitySettingsComponentProps> = ({
allRoles,
dispatch,
}) => {
// useEffect(() => {
// dispatch(exportMetadata());
// }, [dispatch]);
const headers = [
'Role',
'Depth Limit',
'Node Limit',
'Rate Limit (RPM)',
// 'Operation Timeout (Seconds)',
'Timeout (Seconds)',
];
const keys = [
'role',
'depth_limit',
'node_limit',
'rate_limit',
// 'operation_timeout',
'time_limit',
];
const roles = allRoles;
return (

View File

@ -28,6 +28,10 @@ export const labels: Record<
info:
'Set a request rate limit for this role. You can also combine additional unique parameters for more granularity.',
},
time_limit: {
title: 'Timeout',
info: 'Global timeout for GraphQL operations.',
},
};
interface LimitsFormWrapperProps extends TableFormProps<RoleLimits> {
@ -47,6 +51,7 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
const api_limits = useSelector((state: ApiLimitsFormSate) => state);
const rateLimit = useSelector((state: ApiLimitsFormSate) => state.rate_limit);
const nodeLimit = useSelector((state: ApiLimitsFormSate) => state.node_limit);
const timeLimit = useSelector((state: ApiLimitsFormSate) => state.time_limit);
const depthLimit = useSelector(
(state: ApiLimitsFormSate) => state.depth_limit
);
@ -81,6 +86,7 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
const disabled_for_role =
isEmpty(depthLimit?.global) &&
isEmpty(nodeLimit?.global) &&
isEmpty(timeLimit?.global) &&
isEmpty(rateLimit?.global);
return currentRole !== 'global' && disabled_for_role;
};
@ -99,6 +105,8 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
return dispatch(apiLimitActions.updateDepthLimitState(state));
case 'node_limit':
return dispatch(apiLimitActions.updateNodeLimitState(state));
case 'time_limit':
return dispatch(apiLimitActions.updateTimeLimitState(state));
default:
return dispatch(apiLimitActions.updateRateLimitState(state));
}
@ -118,6 +126,10 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
return dispatch(
apiLimitActions.updateNodeLimitRole({ role, limit: value })
);
case 'time_limit':
return dispatch(
apiLimitActions.updateTimeLimitRole({ role, limit: value })
);
default:
return dispatch(
apiLimitActions.updateMaxReqPerMin({ role, limit: value })
@ -129,6 +141,8 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
return dispatch(apiLimitActions.updateGlobalDepthLimit(value));
case 'node_limit':
return dispatch(apiLimitActions.updateGlobalNodeLimit(value));
case 'time_limit':
return dispatch(apiLimitActions.updateGlobalTimeLimit(value));
default:
return dispatch(apiLimitActions.updateGlobalMaxReqPerMin(value));
}
@ -162,6 +176,7 @@ const LimitsFormWrapper: React.FC<LimitsFormWrapperProps> = ({
return (
<LimitsForm
limit={limit}
key={key}
label={label}
role={role}
state={api_limits[limit]?.state ?? RoleState.global}

View File

@ -141,6 +141,7 @@ const LimitsTable: React.FC<Props> = ({
{roles.map(role => (
<TableRow
index={role}
key={role}
entries={getRowData(role)}
renderCol={({ data: { per_role, state } }) => {
const roleLimit = per_role?.[role];

View File

@ -23,6 +23,13 @@ Array [
},
"state": "disabled",
},
Object {
"global": -1,
"per_role": Object {
"user": undefined,
},
"state": "disabled",
},
]
`;
@ -49,6 +56,13 @@ Array [
},
"state": "disabled",
},
Object {
"global": -1,
"per_role": Object {
"new_role": undefined,
},
"state": "disabled",
},
]
`;
@ -75,6 +89,13 @@ Array [
},
"state": "disabled",
},
Object {
"global": -1,
"per_role": Object {
"test_role": undefined,
},
"state": "disabled",
},
]
`;
@ -101,6 +122,13 @@ Array [
},
"state": "disabled",
},
Object {
"global": -1,
"per_role": Object {
"editor": undefined,
},
"state": "disabled",
},
]
`;
@ -121,5 +149,10 @@ Array [
"per_role": Object {},
"state": "disabled",
},
Object {
"global": -1,
"per_role": Object {},
"state": "disabled",
},
]
`;

View File

@ -14,6 +14,7 @@ export type updateSecurityFeaturesActionType = {
disabled: boolean;
depth_limit?: APILimitInputType<number>;
node_limit?: APILimitInputType<number>;
time_limit?: APILimitInputType<number>;
rate_limit?: APILimitInputType<{
unique_params: Nullable<'IP' | string[]>;
max_reqs_per_min: number;

View File

@ -25,6 +25,11 @@ const initialState = {
{ unique_params: Nullable<'IP' | string[]>; max_reqs_per_min: number }
>,
},
time_limit: {
global: -1,
state: RoleState.disabled,
per_role: {} as Record<string, number>,
},
};
type LimitPayload<T = number> = {
@ -51,9 +56,18 @@ const formStateSLice = createSlice({
updateNodeLimitRole(state, action: PayloadAction<LimitPayload>) {
state.node_limit.per_role[action.payload.role] = action.payload.limit;
},
updateTimeLimitRole(state, action: PayloadAction<LimitPayload>) {
state.time_limit.per_role[action.payload.role] = action.payload.limit;
},
updateNodeLimitState(state, action: PayloadAction<RoleState>) {
state.node_limit.state = action.payload;
},
updateTimeLimitState(state, action: PayloadAction<RoleState>) {
state.time_limit.state = action.payload;
},
updateGlobalTimeLimit(state, action: PayloadAction<number>) {
state.time_limit.global = action.payload;
},
updateUniqueParams(
state,
action: PayloadAction<LimitPayload<'IP' | string[]>>
@ -95,6 +109,7 @@ const formStateSLice = createSlice({
state.depth_limit = action.payload.depth_limit;
state.node_limit = action.payload.node_limit;
state.rate_limit = action.payload.rate_limit;
state.time_limit = action.payload.time_limit;
},
setDisable(state, action: PayloadAction<boolean>) {
state.disabled = action.payload;

View File

@ -4,8 +4,9 @@ import { isEmpty } from '../../../Common/utils/jsUtils';
import { ApiLimitsFormSate } from './state';
const getApiLimits = (metadata: HasuraMetadataV3) => {
const { node_limit, depth_limit, rate_limit } = metadata.api_limits ?? {};
return { depth_limit, node_limit, rate_limit };
const { node_limit, depth_limit, rate_limit, time_limit } =
metadata.api_limits ?? {};
return { depth_limit, node_limit, rate_limit, time_limit };
};
export enum RoleState {
@ -44,6 +45,12 @@ const prepareApiLimits = (
state: RoleState.disabled,
};
res.time_limit = {
global: apiLimits?.time_limit?.global ?? -1,
state: RoleState.disabled,
per_role: apiLimits?.time_limit?.per_role ?? {},
};
return res;
};

View File

@ -1001,6 +1001,7 @@ export interface HasuraMetadataV3 {
disabled?: boolean;
depth_limit?: APILimit<number>;
node_limit?: APILimit<number>;
time_limit?: APILimit<number>;
rate_limit?: APILimit<{
unique_params: Nullable<'IP' | string[]>;
max_reqs_per_min: number;

View File

@ -193,6 +193,7 @@ export const updateAPILimitsQuery = ({
disabled: boolean;
depth_limit?: APILimitInputType<number>;
node_limit?: APILimitInputType<number>;
time_limit?: APILimitInputType<number>;
rate_limit?: APILimitInputType<{
unique_params: Nullable<'IP' | string[]>;
max_reqs_per_min: number;
@ -204,7 +205,12 @@ export const updateAPILimitsQuery = ({
disabled: newAPILimits.disabled,
};
const api_limits = ['depth_limit', 'node_limit', 'rate_limit'] as const;
const api_limits = [
'depth_limit',
'node_limit',
'rate_limit',
'time_limit',
] as const;
api_limits.forEach(key => {
const role = newAPILimits[key]?.per_role
@ -281,7 +287,12 @@ export const removeAPILimitsQuery = ({
};
}
const api_limits = ['depth_limit', 'node_limit', 'rate_limit'] as const;
const api_limits = [
'depth_limit',
'node_limit',
'rate_limit',
'time_limit',
] as const;
api_limits.forEach(key => {
delete existingAPILimits?.[key]?.per_role?.[role];