Feature/admin forth/1433/please normalize allowed e.g. #568
Feature/admin forth/1433/please normalize allowed e.g. #568SerVitasik wants to merge 7 commits intonextfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates AdminForth custom action metadata and permission handling by (1) normalizing allowed to support booleans/async checks and (2) aligning the frontend with a boolean “has bulk handler” flag exposed as bulkHandler.
Changes:
- Extend
AdminForthActionInput.allowedto supportbooleanandPromise<boolean>, and normalize booleanallowedvalues in config validation. - Update REST
/get_resourceto filter custom actions byallowedand to exposebulkHandler: trueto the SPA instead ofhasBulkHandler. - Update SPA components/utilities to use
action.bulkHandlerand tighten action typing inThreeDotsMenu.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| adminforth/types/Common.ts | Renames frontend bulk-handler indicator to bulkHandler?: boolean. |
| adminforth/types/Back.ts | Expands allowed type to `boolean |
| adminforth/modules/restApi.ts | Filters actions sent to frontend and adjusts runtime permission checks + frontend action shaping. |
| adminforth/modules/configValidator.ts | Normalizes boolean allowed to a function during validation; improves return type. |
| adminforth/spa/src/utils/utils.ts | Uses action.bulkHandler to decide whether to start custom bulk actions. |
| adminforth/spa/src/views/ListView.vue | Uses action.bulkHandler for bulk success-message selection. |
| adminforth/spa/src/components/ThreeDotsMenu.vue | Removes unused type import and uses AdminForthActionFront for click handling. |
Comments suppressed due to low confidence (2)
adminforth/modules/restApi.ts:2117
- The permission check only runs when
allowedis a function; with the newallowed?: boolean | fntype,allowed: falsewill currently bypass the check and still allow action execution. Treatallowed === falseas not allowed here (and optionally treattrue/undefinedas allowed).
if (typeof action.allowed === 'function') {
const execAllowed = await action.allowed({ adminUser, standardAllowedActions: allowedActions });
if (!execAllowed) {
return { error: await tr(`Action "{actionId}" not allowed`, 'errors', { actionId: action.name }) };
}
adminforth/modules/restApi.ts:2170
- Same as
/start_custom_action: withallowednow supporting booleans,allowed: falsewill bypass this check because it isn't a function and the bulk handler will still execute. Handle booleanallowedvalues explicitly (deny onfalse).
if (typeof action.allowed === 'function') {
const execAllowed = await action.allowed({ adminUser, standardAllowedActions: allowedActions });
if (!execAllowed) {
return { error: await tr(`Action "{actionId}" not allowed`, 'errors', { actionId: action.name }) };
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (typeof action.allowed === 'boolean') { | ||
| const val = action.allowed; | ||
| action.allowed = () => val; | ||
| } |
There was a problem hiding this comment.
allowed boolean normalization is only applied during validateConfig(), but plugins can mutate resource.options.actions later during modifyResourceConfig() (after validation). That means boolean allowed values introduced by plugins won't be normalized here; consider normalizing again after plugin activation (or ensure runtime checks handle boolean allowed consistently).
| if (typeof action.allowed === 'function') { | ||
| const res = await action.allowed({ adminUser, standardAllowedActions: allowedActions }); | ||
| if (res) { | ||
| allowedCustomActions.push(action); | ||
| } | ||
| } else { | ||
| allowedCustomActions.push(action); | ||
| } |
There was a problem hiding this comment.
action.allowed is now typed as boolean | (fn), but this filtering treats any non-function (including false) as allowed and exposes the action to the frontend. This can bypass action-level authorization for configs/plugins that set allowed: false (plugins run after validateConfig, so the boolean->fn normalization may not apply). Handle boolean explicitly (e.g., skip when allowed === false, include when allowed === true/undefined, and only await when it's a function).
| const allowedCustomActions = []; | ||
| if (resource.options.actions) { | ||
| await Promise.all( | ||
| resource.options.actions.map(async (action) => { | ||
| if (typeof action.allowed === 'function') { | ||
| const res = await action.allowed({ adminUser, standardAllowedActions: allowedActions }); | ||
| if (res) { | ||
| allowedCustomActions.push(action); | ||
| } | ||
| } else { | ||
| allowedCustomActions.push(action); | ||
| } | ||
| }) | ||
| ); |
There was a problem hiding this comment.
Promise.all(...map(async ... allowedCustomActions.push(...) )) makes the returned actions order depend on async completion order rather than config order. If allowed checks have varying latency, the UI action ordering can become non-deterministic. Prefer building an array of results (e.g., map to boolean + filter by index) or for...of with await to preserve order.
No description provided.