From b2dff445c957898ac1bc945edd6842840c35770b Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Fri, 13 Feb 2026 19:26:14 +0000 Subject: [PATCH 1/9] Add contact field permission support --- .../contact/ContactDetailsSectionForm.tsx | 15 ++++- .../hooks/useCreateFormFromDefinition.tsx | 1 - plugin-hrm-form/src/permissions/actions.ts | 7 +++ plugin-hrm-form/src/permissions/rules.ts | 60 ++++++++++++++++++- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/plugin-hrm-form/src/components/contact/ContactDetailsSectionForm.tsx b/plugin-hrm-form/src/components/contact/ContactDetailsSectionForm.tsx index 2f64157458..52bd1d9897 100644 --- a/plugin-hrm-form/src/components/contact/ContactDetailsSectionForm.tsx +++ b/plugin-hrm-form/src/components/contact/ContactDetailsSectionForm.tsx @@ -18,11 +18,17 @@ import React from 'react'; import type { FormDefinition, LayoutDefinition } from 'hrm-form-definitions'; import { useFormContext } from 'react-hook-form'; +import { useSelector } from 'react-redux'; import { ColumnarBlock, TwoColumnLayout, Box, BottomButtonBarHeight, ColumnarContent, Container } from '../../styles'; import { disperseInputs, splitAt, splitInHalf } from '../common/forms/formGenerators'; import { useCreateFormFromDefinition } from '../forms'; import { ContactRawJson } from '../../types/types'; +import { RootState } from '../../states'; +import selectContactStateByContactId from '../../states/contacts/selectContactStateByContactId'; +import { getUnsavedContactFromState } from '../../states/contacts/getUnsavedContact'; +import { getInitializedCan } from '../../permissions/rules'; +import { ContactFieldActions } from '../../permissions/actions'; type OwnProps = { display: boolean; @@ -53,15 +59,22 @@ const ContactDetailsSectionForm: React.FC = ({ extraChildrenRight, contactId, }) => { + const contact = useSelector((state: RootState) => + getUnsavedContactFromState(selectContactStateByContactId(state, contactId)), + ); + const can = getInitializedCan(); + const canView = (field: string) => can(ContactFieldActions.VIEW_CONTACT_FIELD, { contact, field }); + const canEdit = (field: string) => can(ContactFieldActions.EDIT_CONTACT_FIELD, { contact, field }); const { getValues } = useFormContext(); const form = useCreateFormFromDefinition({ - definition, + definition: definition.filter(item => canView(`rawJson.${tabPath}.${item.name}`)), initialValues, parentsPath: tabPath, updateCallback: () => { updateForm(getValues()); }, + isItemEnabled: item => canEdit(`rawJson.${tabPath}.${item.name}`), shouldFocusFirstElement: display && autoFocus, context: { contactId }, }); diff --git a/plugin-hrm-form/src/components/forms/hooks/useCreateFormFromDefinition.tsx b/plugin-hrm-form/src/components/forms/hooks/useCreateFormFromDefinition.tsx index 070dde9fbb..3db7992f75 100644 --- a/plugin-hrm-form/src/components/forms/hooks/useCreateFormFromDefinition.tsx +++ b/plugin-hrm-form/src/components/forms/hooks/useCreateFormFromDefinition.tsx @@ -49,7 +49,6 @@ const useCreateFormFromDefinition = ({ }: UseFormFromDefinition) => { const firstElementRef = useFocus(shouldFocusFirstElement); if (!initialValues) return []; - return definition.map((e: FormItemDefinition, index: number) => { const elementRef = index === 0 ? firstElementRef : null; const maybeValue = get(initialValues, e.name); diff --git a/plugin-hrm-form/src/permissions/actions.ts b/plugin-hrm-form/src/permissions/actions.ts index 604cb77352..d068beb061 100644 --- a/plugin-hrm-form/src/permissions/actions.ts +++ b/plugin-hrm-form/src/permissions/actions.ts @@ -35,6 +35,11 @@ export const ContactActions = { REMOVE_CONTACT_FROM_CASE: 'removeContactFromCase', } as const; +export const ContactFieldActions = { + VIEW_CONTACT_FIELD: 'viewContactField', + EDIT_CONTACT_FIELD: 'editContactField', +} as const; + export const ProfileActions = { VIEW_PROFILE: 'viewProfile', // EDIT_PROFILE: 'editProfile', // we don't need edit for now, will be needed when users can attach more identifiers or edit the name @@ -55,6 +60,7 @@ export const ViewIdentifiersAction = { export const PermissionActions = { ...CaseActions, ...ContactActions, + ...ContactFieldActions, ...ProfileActions, ...ProfileSectionActions, ...ViewIdentifiersAction, @@ -63,6 +69,7 @@ export const PermissionActions = { export const actionsMaps = { case: CaseActions, contact: ContactActions, + contactField: ContactFieldActions, profile: ProfileActions, profileSection: ProfileSectionActions, postSurvey: { diff --git a/plugin-hrm-form/src/permissions/rules.ts b/plugin-hrm-form/src/permissions/rules.ts index 8da34155bc..4a3120aba7 100644 --- a/plugin-hrm-form/src/permissions/rules.ts +++ b/plugin-hrm-form/src/permissions/rules.ts @@ -18,7 +18,7 @@ import parseISO from 'date-fns/parseISO'; import { differenceInDays, differenceInHours } from 'date-fns'; import { getHrmConfig } from '../hrmConfig'; -import { ProfileSection } from '../types/types'; +import {ContactRawJson, ProfileSection} from '../types/types'; import { fetchPermissionRules } from '../services/PermissionsService'; import { actionsMaps, PermissionActions, TargetKind } from './actions'; @@ -52,6 +52,19 @@ type ContactSpecificCondition = typeof contactSpecificConditions[number]; const isContactSpecificCondition = (c: any): c is ContactSpecificCondition => typeof c === 'string' && contactSpecificConditions.includes(c as any); +type ContactFieldSpecificCondition = { + // eslint-disable-next-line prettier/prettier + field: `rawJson.${keyof ContactRawJson}.${string}`; +}; + +const isContactFieldSpecificCondition = (c: any): c is ContactFieldSpecificCondition => { + if (typeof c?.field === 'string') { + const [root, ...path] = c.field.split('.'); + return root === 'rawJson' && path.length === 2; + } + + return false; +}; const caseSpecificConditions = ['isCreator', 'isCaseOpen', 'isCaseContactOwner'] as const; type CaseSpecificCondition = typeof caseSpecificConditions[number]; @@ -77,6 +90,11 @@ type SupportedContactCondition = TimeBasedCondition | UserBasedCondition | Conta const isSupportedContactCondition = (c: any): c is SupportedContactCondition => isTimeBasedCondition(c) || isUserBasedCondition(c) || isContactSpecificCondition(c); + +type SupportedContactFieldCondition = SupportedContactCondition; +const isSupportedContactFieldCondition = (c: any): c is SupportedContactFieldCondition => + isSupportedContactCondition(c) || isContactFieldSpecificCondition(c); + type SupportedCaseCondition = TimeBasedCondition | UserBasedCondition | CaseSpecificCondition; const isSupportedCaseCondition = (c: any): c is SupportedCaseCondition => isTimeBasedCondition(c) || isUserBasedCondition(c) || isCaseSpecificCondition(c); @@ -99,6 +117,7 @@ const isSupportedViewIdentifiersCondition = (c: any): c is SupportedPostSurveyCo // Defines which actions are supported on each TargetKind type SupportedTKCondition = { contact: SupportedContactCondition; + contactField: SupportedContactFieldCondition; case: SupportedCaseCondition; profile: SupportedProfileCondition; profileSection: SupportedProfileSectionCondition; @@ -147,6 +166,9 @@ const isTKCondition = (kind: T) => (c: any): c is TKCondit case 'contact': { return isSupportedContactCondition(c); } + case 'contactField': { + return isSupportedContactFieldCondition(c); + } case 'case': { return isSupportedCaseCondition(c); } @@ -275,6 +297,24 @@ const applyProfileSectionSpecificConditions = (conditions: ProfileSectionSpecifi return accum; }, {}); +const applyContactFieldSpecificConditions = (conditions: ContactFieldSpecificCondition[]) => ( + field: ContactFieldSpecificCondition['field'], +) => + conditions + .map(c => Object.entries(c)[0]) + .reduce>((accum, [cond, param]) => { + // use the stringified cond-param as key, e.g. '{ "sectionType": "summary" }' + if (cond === 'field') { + const key = JSON.stringify({ [cond]: param }); + return { + ...accum, + [key]: field === param, + }; + } + + return accum; + }, {}); + const setupAllow = (kind: T, conditionsSets: TKConditionsSets) => { // We could do type validation on target depending on targetKind if we ever want to make sure the "allow" is called on a proper target (same as cancan used to do) @@ -311,6 +351,24 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS return checkConditionsSets(conditionsState, conditionsSets); } + case 'contactField': { + const specificConditions = conditionsSets.flatMap(cs => + cs.map(c => (isContactFieldSpecificCondition(c) ? c : null)).filter(c => c !== null), + ); + const { contact, field } = target; + const appliedSpecificConditions = applyContactFieldSpecificConditions(specificConditions)(field); + const conditionsState: ConditionsState = { + isSupervisor: performer.isSupervisor, + isOwner: isContactOwner(performer, contact), + everyone: true, + createdDaysAgo: false, + createdHoursAgo: false, + ...appliedTimeBasedConditions, + ...appliedSpecificConditions, + }; + + return checkConditionsSets(conditionsState, conditionsSets); + } case 'postSurvey': case 'profile': { const conditionsState: ConditionsState = { From e55caaedf2e4659bdd5b69a46586fae56ff7d5a1 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 16 Feb 2026 15:49:24 +0000 Subject: [PATCH 2/9] Fix contact field permissions check --- plugin-hrm-form/src/permissions/rules.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin-hrm-form/src/permissions/rules.ts b/plugin-hrm-form/src/permissions/rules.ts index 4a3120aba7..edbbd002b8 100644 --- a/plugin-hrm-form/src/permissions/rules.ts +++ b/plugin-hrm-form/src/permissions/rules.ts @@ -352,11 +352,10 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS return checkConditionsSets(conditionsState, conditionsSets); } case 'contactField': { - const specificConditions = conditionsSets.flatMap(cs => - cs.map(c => (isContactFieldSpecificCondition(c) ? c : null)).filter(c => c !== null), - ); const { contact, field } = target; - const appliedSpecificConditions = applyContactFieldSpecificConditions(specificConditions)(field); + // Filter out any condition sets that apply specivfically to a field other than this one + // Keep only global conditions and ones that apply to this field + const applicableConditions = conditionsSets.filter(css => !css.some(cs => isContactFieldSpecificCondition(cs) && cs.field !== field)); const conditionsState: ConditionsState = { isSupervisor: performer.isSupervisor, isOwner: isContactOwner(performer, contact), @@ -364,10 +363,10 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS createdDaysAgo: false, createdHoursAgo: false, ...appliedTimeBasedConditions, - ...appliedSpecificConditions, + [JSON.stringify({ field })]: true, }; - return checkConditionsSets(conditionsState, conditionsSets); + return checkConditionsSets(conditionsState, applicableConditions); } case 'postSurvey': case 'profile': { From e31ad6763f2c5a9886cd05f1de45c55bc84d6bee Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 16 Feb 2026 16:40:08 +0000 Subject: [PATCH 3/9] Fix contact field permissions check --- plugin-hrm-form/src/permissions/rules.ts | 28 +++++++----------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/plugin-hrm-form/src/permissions/rules.ts b/plugin-hrm-form/src/permissions/rules.ts index edbbd002b8..97ae11ef42 100644 --- a/plugin-hrm-form/src/permissions/rules.ts +++ b/plugin-hrm-form/src/permissions/rules.ts @@ -297,24 +297,6 @@ const applyProfileSectionSpecificConditions = (conditions: ProfileSectionSpecifi return accum; }, {}); -const applyContactFieldSpecificConditions = (conditions: ContactFieldSpecificCondition[]) => ( - field: ContactFieldSpecificCondition['field'], -) => - conditions - .map(c => Object.entries(c)[0]) - .reduce>((accum, [cond, param]) => { - // use the stringified cond-param as key, e.g. '{ "sectionType": "summary" }' - if (cond === 'field') { - const key = JSON.stringify({ [cond]: param }); - return { - ...accum, - [key]: field === param, - }; - } - - return accum; - }, {}); - const setupAllow = (kind: T, conditionsSets: TKConditionsSets) => { // We could do type validation on target depending on targetKind if we ever want to make sure the "allow" is called on a proper target (same as cancan used to do) @@ -355,7 +337,13 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS const { contact, field } = target; // Filter out any condition sets that apply specivfically to a field other than this one // Keep only global conditions and ones that apply to this field - const applicableConditions = conditionsSets.filter(css => !css.some(cs => isContactFieldSpecificCondition(cs) && cs.field !== field)); + const applicableConditionSets = + conditionsSets.filter(css => !css.some(cs => isContactFieldSpecificCondition(cs) && cs.field !== field)); + + // If there is not at least 1 condition set specific top this field, allow it. + // Fields that aren't specifically called out in the permissions are assumed permitted + const hasAnyFieldSpecificConditionSets = applicableConditionSets.some(acs => acs.some(isContactFieldSpecificCondition)) + if (!hasAnyFieldSpecificConditionSets) return true; const conditionsState: ConditionsState = { isSupervisor: performer.isSupervisor, isOwner: isContactOwner(performer, contact), @@ -366,7 +354,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS [JSON.stringify({ field })]: true, }; - return checkConditionsSets(conditionsState, applicableConditions); + return checkConditionsSets(conditionsState, applicableConditionSets); } case 'postSurvey': case 'profile': { From 1dec2c0d6bfe20e4c8c75023004b00fc8ece3251 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:05:56 +0000 Subject: [PATCH 4/9] Initial plan From 74c35dd4badbf0c5a67a9612477e0e2cb8613573 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:12:22 +0000 Subject: [PATCH 5/9] Add comprehensive ContactField permission tests Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../src/___tests__/permissions/rules.test.ts | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts index b48602fc4d..8e0e8a0192 100644 --- a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts +++ b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts @@ -25,6 +25,7 @@ import { actionsMaps, CaseActions, ContactActions, + ContactFieldActions, PermissionActions, ProfileActions, ProfileSectionActions, @@ -612,3 +613,262 @@ describe('ViewIdentifiersAction', () => { }, ); }); + +describe('ContactFieldActions', () => { + afterEach(() => { + cleanupInitializedCan(); + }); + + const mockContact = { twilioWorkerId: 'owner', createdAt: new Date().toISOString() }; + const testField = 'rawJson.childInformation.name'; + const otherField = 'rawJson.childInformation.age'; + + each( + Object.values(ContactFieldActions) + .flatMap(action => [ + // Test 1: Fields not specified by a 'field' condition in any condition set are always allowed + { + action, + conditionsSets: [ + ['isSupervisor'], + [{ field: otherField }], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'field is not specified in any condition set - should be allowed', + }, + { + action, + conditionsSets: [ + [{ field: otherField }], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'field is not specified (only other field specified) - should be allowed', + }, + // Test 2: Condition sets with no field condition will allow any field if they evaluate to true + { + action, + conditionsSets: [ + ['everyone'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'no field condition, everyone condition evaluates to true', + }, + { + action, + conditionsSets: [ + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: true, + field: testField, + expectedResult: true, + expectedDescription: 'no field condition, isSupervisor condition evaluates to true', + }, + { + action, + conditionsSets: [ + ['isOwner'], + ], + workerSid: 'owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'no field condition, isOwner condition evaluates to true', + }, + // Test 3: If condition sets with no field condition evaluate to false, but there are no condition sets + // with a field condition matching the field being checked, the action should be allowed + { + action, + conditionsSets: [ + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'no field-specific conditions exist, so field is allowed even though isSupervisor is false', + }, + { + action, + conditionsSets: [ + ['isOwner'], + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'no field-specific conditions exist, field is allowed even though conditions are false', + }, + { + action, + conditionsSets: [ + ['isSupervisor'], + [{ field: otherField }], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'field has no specific conditions (only other field specified), allowed even though isSupervisor is false', + }, + // Test 4: Multiple field conditions in the same set specifying different fields means + // that condition set will be filtered out, demonstrating it can't restrict a field on its own + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'multiple different field conditions in same set gets filtered out, no field-specific conditions remain, so allowed', + }, + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + ['everyone'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'conflicting field conditions get filtered out, everyone condition allows', + }, + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: true, + field: testField, + expectedResult: true, + expectedDescription: 'conflicting field conditions get filtered out, isSupervisor allows', + }, + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'conflicting field conditions get filtered out, no field-specific conditions remain even though isSupervisor is false, so allowed', + }, + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + [{ field: testField }, 'isOwner'], + ], + workerSid: 'owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'conflicting field conditions filtered out, but valid field condition with isOwner passes', + }, + { + action, + conditionsSets: [ + [{ field: testField }, { field: otherField }], + [{ field: testField }, 'isOwner'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: false, + expectedDescription: 'conflicting field conditions filtered out, valid field condition exists but isOwner is false', + }, + // Additional comprehensive tests + { + action, + conditionsSets: [ + [{ field: testField }, 'isOwner'], + ], + workerSid: 'owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'field condition matches and isOwner is true', + }, + { + action, + conditionsSets: [ + [{ field: testField }, 'isOwner'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: false, + expectedDescription: 'field condition matches but isOwner is false', + }, + { + action, + conditionsSets: [ + [{ field: testField }], + ['isSupervisor'], + ], + workerSid: 'not owner', + isSupervisor: true, + field: testField, + expectedResult: true, + expectedDescription: 'field condition in first set, isSupervisor in second set evaluates to true', + }, + { + action, + conditionsSets: [ + [{ field: testField }], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'only field condition with no additional conditions - always true', + }, + { + action, + conditionsSets: [ + [{ field: otherField }, 'everyone'], + ], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: true, + expectedDescription: 'other field condition with everyone - field not specified so allowed', + }, + ]) + .map(addPrettyPrintConditions), + ).test( + `Should return $expectedResult for action $action when $expectedDescription and conditionsSets are $prettyConditionsSets`, + async ({ action, conditionsSets, workerSid, isSupervisor, field, expectedResult }) => { + const rules = buildRules(conditionsSets, 'contactField'); + fetchPermissionRulesSpy.mockResolvedValue(rules); + + mockPartialConfiguration({ + workerSid, + isSupervisor, + }); + + await validateAndSetPermissionRules(); + + const can = getInitializedCan(); + + expect(can(action, { contact: mockContact, field })).toBe(expectedResult); + }, + ); +}); From 5bbc33b6e59651e5028d22c03a599f8a5e4c84c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:13:33 +0000 Subject: [PATCH 6/9] Fix ESLint formatting issues in rules.test.ts Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../src/___tests__/permissions/rules.test.ts | 97 ++++++------------- 1 file changed, 31 insertions(+), 66 deletions(-) diff --git a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts index 8e0e8a0192..0e3e4721eb 100644 --- a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts +++ b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts @@ -629,10 +629,7 @@ describe('ContactFieldActions', () => { // Test 1: Fields not specified by a 'field' condition in any condition set are always allowed { action, - conditionsSets: [ - ['isSupervisor'], - [{ field: otherField }], - ], + conditionsSets: [['isSupervisor'], [{ field: otherField }]], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -641,9 +638,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: otherField }], - ], + conditionsSets: [[{ field: otherField }]], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -653,9 +648,7 @@ describe('ContactFieldActions', () => { // Test 2: Condition sets with no field condition will allow any field if they evaluate to true { action, - conditionsSets: [ - ['everyone'], - ], + conditionsSets: [['everyone']], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -664,9 +657,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - ['isSupervisor'], - ], + conditionsSets: [['isSupervisor']], workerSid: 'not owner', isSupervisor: true, field: testField, @@ -675,34 +666,28 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - ['isOwner'], - ], + conditionsSets: [['isOwner']], workerSid: 'owner', isSupervisor: false, field: testField, expectedResult: true, expectedDescription: 'no field condition, isOwner condition evaluates to true', }, - // Test 3: If condition sets with no field condition evaluate to false, but there are no condition sets + // Test 3: If condition sets with no field condition evaluate to false, but there are no condition sets // with a field condition matching the field being checked, the action should be allowed { action, - conditionsSets: [ - ['isSupervisor'], - ], + conditionsSets: [['isSupervisor']], workerSid: 'not owner', isSupervisor: false, field: testField, expectedResult: true, - expectedDescription: 'no field-specific conditions exist, so field is allowed even though isSupervisor is false', + expectedDescription: + 'no field-specific conditions exist, so field is allowed even though isSupervisor is false', }, { action, - conditionsSets: [ - ['isOwner'], - ['isSupervisor'], - ], + conditionsSets: [['isOwner'], ['isSupervisor']], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -711,35 +696,29 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - ['isSupervisor'], - [{ field: otherField }], - ], + conditionsSets: [['isSupervisor'], [{ field: otherField }]], workerSid: 'not owner', isSupervisor: false, field: testField, expectedResult: true, - expectedDescription: 'field has no specific conditions (only other field specified), allowed even though isSupervisor is false', + expectedDescription: + 'field has no specific conditions (only other field specified), allowed even though isSupervisor is false', }, - // Test 4: Multiple field conditions in the same set specifying different fields means + // Test 4: Multiple field conditions in the same set specifying different fields means // that condition set will be filtered out, demonstrating it can't restrict a field on its own { action, - conditionsSets: [ - [{ field: testField }, { field: otherField }], - ], + conditionsSets: [[{ field: testField }, { field: otherField }]], workerSid: 'not owner', isSupervisor: false, field: testField, expectedResult: true, - expectedDescription: 'multiple different field conditions in same set gets filtered out, no field-specific conditions remain, so allowed', + expectedDescription: + 'multiple different field conditions in same set gets filtered out, no field-specific conditions remain, so allowed', }, { action, - conditionsSets: [ - [{ field: testField }, { field: otherField }], - ['everyone'], - ], + conditionsSets: [[{ field: testField }, { field: otherField }], ['everyone']], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -748,10 +727,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: testField }, { field: otherField }], - ['isSupervisor'], - ], + conditionsSets: [[{ field: testField }, { field: otherField }], ['isSupervisor']], workerSid: 'not owner', isSupervisor: true, field: testField, @@ -760,15 +736,13 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: testField }, { field: otherField }], - ['isSupervisor'], - ], + conditionsSets: [[{ field: testField }, { field: otherField }], ['isSupervisor']], workerSid: 'not owner', isSupervisor: false, field: testField, expectedResult: true, - expectedDescription: 'conflicting field conditions get filtered out, no field-specific conditions remain even though isSupervisor is false, so allowed', + expectedDescription: + 'conflicting field conditions get filtered out, no field-specific conditions remain even though isSupervisor is false, so allowed', }, { action, @@ -780,7 +754,8 @@ describe('ContactFieldActions', () => { isSupervisor: false, field: testField, expectedResult: true, - expectedDescription: 'conflicting field conditions filtered out, but valid field condition with isOwner passes', + expectedDescription: + 'conflicting field conditions filtered out, but valid field condition with isOwner passes', }, { action, @@ -792,14 +767,13 @@ describe('ContactFieldActions', () => { isSupervisor: false, field: testField, expectedResult: false, - expectedDescription: 'conflicting field conditions filtered out, valid field condition exists but isOwner is false', + expectedDescription: + 'conflicting field conditions filtered out, valid field condition exists but isOwner is false', }, // Additional comprehensive tests { action, - conditionsSets: [ - [{ field: testField }, 'isOwner'], - ], + conditionsSets: [[{ field: testField }, 'isOwner']], workerSid: 'owner', isSupervisor: false, field: testField, @@ -808,9 +782,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: testField }, 'isOwner'], - ], + conditionsSets: [[{ field: testField }, 'isOwner']], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -819,10 +791,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: testField }], - ['isSupervisor'], - ], + conditionsSets: [[{ field: testField }], ['isSupervisor']], workerSid: 'not owner', isSupervisor: true, field: testField, @@ -831,9 +800,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: testField }], - ], + conditionsSets: [[{ field: testField }]], workerSid: 'not owner', isSupervisor: false, field: testField, @@ -842,9 +809,7 @@ describe('ContactFieldActions', () => { }, { action, - conditionsSets: [ - [{ field: otherField }, 'everyone'], - ], + conditionsSets: [[{ field: otherField }, 'everyone']], workerSid: 'not owner', isSupervisor: false, field: testField, From 418633651da819d6f1d6bbb99c87b8afd27f89e6 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Tue, 17 Feb 2026 15:20:47 +0000 Subject: [PATCH 7/9] support for 'nobody' condition --- plugin-hrm-form/src/permissions/rules.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugin-hrm-form/src/permissions/rules.ts b/plugin-hrm-form/src/permissions/rules.ts index 97ae11ef42..fec74a4bb0 100644 --- a/plugin-hrm-form/src/permissions/rules.ts +++ b/plugin-hrm-form/src/permissions/rules.ts @@ -41,7 +41,7 @@ const isTimeBasedCondition = (c: any): c is TimeBasedCondition => { return false; }; -const userBasedConditions = ['isSupervisor', 'everyone'] as const; +const userBasedConditions = ['isSupervisor', 'everyone', 'nobody'] as const; type UserBasedCondition = typeof userBasedConditions[number]; const isUserBasedCondition = (c: any): c is UserBasedCondition => @@ -316,6 +316,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS isCaseOpen: isCaseOpen(target), isCaseContactOwner: isCaseContactOwner(target), everyone: true, + nobody: false, ...appliedTimeBasedConditions, }; @@ -326,6 +327,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS isSupervisor: performer.isSupervisor, isOwner: isContactOwner(performer, target), everyone: true, + nobody: false, createdDaysAgo: false, createdHoursAgo: false, ...appliedTimeBasedConditions, @@ -348,6 +350,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS isSupervisor: performer.isSupervisor, isOwner: isContactOwner(performer, contact), everyone: true, + nobody: false, createdDaysAgo: false, createdHoursAgo: false, ...appliedTimeBasedConditions, @@ -361,6 +364,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS const conditionsState: ConditionsState = { isSupervisor: performer.isSupervisor, everyone: true, + nobody: false, ...appliedTimeBasedConditions, }; @@ -376,6 +380,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS const conditionsState: ConditionsState = { isSupervisor: performer.isSupervisor, everyone: true, + nobody: false, ...appliedTimeBasedConditions, ...appliedSpecificConditions, }; @@ -386,6 +391,7 @@ const setupAllow = (kind: T, conditionsSets: TKConditionsS const conditionsState: ConditionsState = { isSupervisor: performer.isSupervisor, everyone: true, + nobody: false, }; return checkConditionsSets(conditionsState, conditionsSets); From 8893d511f082dd35bee71f0a7271fe358a44cb30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:57:37 +0000 Subject: [PATCH 8/9] Initial plan From 3c7b4c2699c1ae67bab23bd7cfdcc6a5f37856aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:07:24 +0000 Subject: [PATCH 9/9] Add comprehensive 'nobody' condition test coverage for all permission target kinds Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- lambdas/package-lock.json | 128 +++++++-------- .../src/___tests__/permissions/rules.test.ts | 148 ++++++++++++++++++ 2 files changed, 208 insertions(+), 68 deletions(-) diff --git a/lambdas/package-lock.json b/lambdas/package-lock.json index 8e4a209ad1..66ac8d1190 100644 --- a/lambdas/package-lock.json +++ b/lambdas/package-lock.json @@ -6376,6 +6376,7 @@ "version": "3.716.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.716.0.tgz", "integrity": "sha512-lA4IB9FzR2KjH7EVCo+mHGFKqdViVyeBQEIX9oVratL/l7P0bMS1fMwgfHOc3ACazqNxBxDES7x08ZCp32y6Lw==", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -6428,6 +6429,7 @@ "version": "3.716.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.716.0.tgz", "integrity": "sha512-i4SVNsrdXudp8T4bkm7Fi3YWlRnvXCSwvNDqf6nLqSJxqr4CN3VlBELueDyjBK7TAt453/qSif+eNx+bHmwo4Q==", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -9623,6 +9625,7 @@ "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", @@ -12633,6 +12636,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", "dev": true, + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -14109,6 +14113,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -14143,6 +14148,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -14324,6 +14330,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "devOptional": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -14497,7 +14504,6 @@ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -14560,7 +14566,6 @@ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14639,7 +14644,6 @@ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14690,8 +14694,7 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/astral-regex": { "version": "2.0.0", @@ -14852,7 +14855,6 @@ "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", "dev": true, "license": "MPL-2.0", - "peer": true, "engines": { "node": ">=4" } @@ -14874,7 +14876,6 @@ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -15540,6 +15541,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -16044,8 +16046,7 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", @@ -16614,7 +16615,6 @@ "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -16807,6 +16807,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -16905,6 +16906,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -16988,6 +16990,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -17075,7 +17078,6 @@ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", @@ -17105,8 +17107,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", @@ -17144,7 +17145,6 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -17192,7 +17192,6 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -17206,7 +17205,6 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -17217,7 +17215,6 @@ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -17236,7 +17233,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -18178,6 +18174,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.9.0.tgz", "integrity": "sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA==", "dev": true, + "peer": true, "engines": { "node": ">= 10.x" } @@ -19159,7 +19156,6 @@ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -19176,6 +19172,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "peer": true, "dependencies": { "@jest/core": "^28.1.3", "@jest/types": "^28.1.3", @@ -21043,7 +21040,6 @@ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -21097,8 +21093,7 @@ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", "dev": true, - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/language-tags": { "version": "1.0.9", @@ -21106,7 +21101,6 @@ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "language-subtag-registry": "^0.3.20" }, @@ -22451,6 +22445,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -22557,7 +22552,6 @@ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -22569,8 +22563,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -23702,7 +23695,6 @@ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -23718,7 +23710,6 @@ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -23765,7 +23756,6 @@ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -24932,6 +24922,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -25492,6 +25483,7 @@ "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" @@ -25760,6 +25752,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -26030,6 +26023,7 @@ "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -26594,6 +26588,7 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -26685,6 +26680,7 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -31238,6 +31234,7 @@ "version": "3.716.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.716.0.tgz", "integrity": "sha512-lA4IB9FzR2KjH7EVCo+mHGFKqdViVyeBQEIX9oVratL/l7P0bMS1fMwgfHOc3ACazqNxBxDES7x08ZCp32y6Lw==", + "peer": true, "requires": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -31284,6 +31281,7 @@ "version": "3.716.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.716.0.tgz", "integrity": "sha512-i4SVNsrdXudp8T4bkm7Fi3YWlRnvXCSwvNDqf6nLqSJxqr4CN3VlBELueDyjBK7TAt453/qSif+eNx+bHmwo4Q==", + "peer": true, "requires": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -33666,6 +33664,7 @@ "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", @@ -35611,6 +35610,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", "dev": true, + "peer": true, "requires": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -36774,6 +36774,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, + "peer": true, "requires": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" @@ -36934,6 +36935,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -37117,6 +37119,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, + "peer": true, "requires": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -37546,7 +37549,8 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true + "dev": true, + "peer": true }, "pretty-format": { "version": "27.5.1", @@ -37588,7 +37592,8 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "dev": true, + "peer": true }, "v8-to-istanbul": { "version": "8.1.1", @@ -38644,6 +38649,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, + "peer": true, "requires": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -38662,6 +38668,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -38764,7 +38771,8 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "devOptional": true + "devOptional": true, + "peer": true }, "acorn-globals": { "version": "6.0.0", @@ -38882,8 +38890,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "peer": true + "dev": true }, "array-buffer-byte-length": { "version": "1.0.2", @@ -38926,7 +38933,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -38979,7 +38985,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -39016,8 +39021,7 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "peer": true + "dev": true }, "astral-regex": { "version": "2.0.0", @@ -39138,8 +39142,7 @@ "version": "4.10.3", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "dev": true, - "peer": true + "dev": true }, "axios": { "version": "1.13.2", @@ -39155,8 +39158,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "peer": true + "dev": true }, "babel-code-frame": { "version": "6.26.0", @@ -39668,6 +39670,7 @@ "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "peer": true, "requires": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -40037,8 +40040,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "peer": true + "dev": true }, "data-uri-to-buffer": { "version": "6.0.2", @@ -40456,7 +40458,6 @@ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -40602,6 +40603,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -40695,6 +40697,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, + "peer": true, "requires": {} }, "eslint-import-resolver-node": { @@ -40759,6 +40762,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -40819,7 +40823,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, - "peer": true, "requires": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", @@ -40842,8 +40845,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true + "dev": true } } }, @@ -40862,7 +40864,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -40889,7 +40890,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -40898,15 +40898,13 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true + "dev": true }, "resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, - "peer": true, "requires": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -40917,8 +40915,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -41581,7 +41578,8 @@ "version": "15.9.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.9.0.tgz", "integrity": "sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA==", - "dev": true + "dev": true, + "peer": true }, "graphql-http": { "version": "1.22.3", @@ -42259,7 +42257,6 @@ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, - "peer": true, "requires": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -42273,6 +42270,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "peer": true, "requires": { "@jest/core": "^28.1.3", "@jest/types": "^28.1.3", @@ -43687,7 +43685,6 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -43732,15 +43729,13 @@ "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "peer": true + "dev": true }, "language-tags": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, - "peer": true, "requires": { "language-subtag-registry": "^0.3.20" } @@ -44739,7 +44734,8 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true + "dev": true, + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -44818,7 +44814,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, - "peer": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -44829,8 +44824,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -45718,7 +45712,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -45730,7 +45723,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -45764,7 +45756,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, - "peer": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -46636,6 +46627,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "peer": true, "requires": {} }, "xml": { diff --git a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts index 0e3e4721eb..7f308e8415 100644 --- a/plugin-hrm-form/src/___tests__/permissions/rules.test.ts +++ b/plugin-hrm-form/src/___tests__/permissions/rules.test.ts @@ -208,6 +208,30 @@ describe('CasesActions', () => { expectedDescription: 'created less than 1 day ago', createdAt: subDays(new Date(), 2).toISOString(), }, + { + action, + conditionsSets: [['nobody']], + workerSid: NOT_CREATOR_WORKER_SID, + isSupervisor: false, + expectedResult: false, + expectedDescription: 'nobody condition always denies', + }, + { + action, + conditionsSets: [['nobody'], ['isSupervisor']], + workerSid: NOT_CREATOR_WORKER_SID, + isSupervisor: true, + expectedResult: true, + expectedDescription: 'nobody in first set, but isSupervisor in second set allows', + }, + { + action, + conditionsSets: [['nobody', 'isCreator']], + workerSid: 'creator', + isSupervisor: false, + expectedResult: false, + expectedDescription: 'nobody combined with isCreator still denies', + }, ]) .map(addPrettyPrintConditions), ).test( @@ -338,6 +362,30 @@ describe('ContactActions', () => { expectedDescription: 'created more than 1 day ago', createdAt: subDays(new Date(), 2).toISOString(), }, + { + action, + conditionsSets: [['nobody']], + workerSid: 'owner', + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody condition always denies', + }, + { + action, + conditionsSets: [['nobody'], ['isOwner']], + workerSid: 'owner', + isSupervisor: false, + expectedResult: true, + expectedDescription: 'nobody in first set, but isOwner in second set allows', + }, + { + action, + conditionsSets: [['nobody', 'isOwner']], + workerSid: 'owner', + isSupervisor: false, + expectedResult: false, + expectedDescription: 'nobody combined with isOwner still denies', + }, ]) .map(addPrettyPrintConditions), ).test( @@ -421,6 +469,27 @@ describe('ProfileActions', () => { expectedDescription: 'created more than 1 day ago', createdAt: subDays(new Date(), 2).toISOString(), }, + { + action, + conditionsSets: [['nobody']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody condition always denies', + }, + { + action, + conditionsSets: [['nobody'], ['isSupervisor']], + isSupervisor: true, + expectedResult: true, + expectedDescription: 'nobody in first set, but isSupervisor in second set allows', + }, + { + action, + conditionsSets: [['nobody', 'isSupervisor']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody combined with isSupervisor still denies', + }, ]) .map(addPrettyPrintConditions), ).test( @@ -520,6 +589,27 @@ describe('ProfileSectionActions', () => { expectedDescription: 'sectionType does not matches', sectionType: 'something else', }, + { + action, + conditionsSets: [['nobody']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody condition always denies', + }, + { + action, + conditionsSets: [['nobody'], ['isSupervisor']], + isSupervisor: true, + expectedResult: true, + expectedDescription: 'nobody in first set, but isSupervisor in second set allows', + }, + { + action, + conditionsSets: [['nobody', 'isSupervisor']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody combined with isSupervisor still denies', + }, ]) .map(addPrettyPrintConditions), ).test( @@ -592,6 +682,27 @@ describe('ViewIdentifiersAction', () => { expectedResult: false, expectedDescription: 'user is not a supervisor', }, + { + action, + conditionsSets: [['nobody']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody condition always denies', + }, + { + action, + conditionsSets: [['nobody'], ['isSupervisor']], + isSupervisor: true, + expectedResult: true, + expectedDescription: 'nobody in first set, but isSupervisor in second set allows', + }, + { + action, + conditionsSets: [['nobody', 'isSupervisor']], + isSupervisor: true, + expectedResult: false, + expectedDescription: 'nobody combined with isSupervisor still denies', + }, ]) .map(addPrettyPrintConditions), ).test( @@ -816,6 +927,43 @@ describe('ContactFieldActions', () => { expectedResult: true, expectedDescription: 'other field condition with everyone - field not specified so allowed', }, + // Test 5: 'nobody' condition tests + { + action, + conditionsSets: [['nobody']], + workerSid: 'owner', + isSupervisor: true, + field: testField, + expectedResult: true, + expectedDescription: 'nobody with no field condition - field not restricted so allowed', + }, + { + action, + conditionsSets: [[{ field: testField }, 'nobody']], + workerSid: 'owner', + isSupervisor: true, + field: testField, + expectedResult: false, + expectedDescription: 'nobody combined with field condition denies', + }, + { + action, + conditionsSets: [[{ field: testField }, 'nobody'], ['isSupervisor']], + workerSid: 'not owner', + isSupervisor: true, + field: testField, + expectedResult: true, + expectedDescription: 'nobody with field in first set, but isSupervisor in second set allows', + }, + { + action, + conditionsSets: [[{ field: testField }, 'nobody'], ['isOwner']], + workerSid: 'not owner', + isSupervisor: false, + field: testField, + expectedResult: false, + expectedDescription: 'nobody with field in first set denies, isOwner false in second set', + }, ]) .map(addPrettyPrintConditions), ).test(