mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
`campaignJsonMutationRoute` now builds every JSON POST route under /campanha — `isSameOriginRequest` had been copied into five of them, so the CSRF policy was failing open by omission; a convention guard sweeps all of `src/app` and names its three exceptions. `postCampaignJson` is the only place that spells `credentials: 'same-origin'`, and `useCampaignCellAutosave` is the debounce / abort / revert / flush-on-close machine the quick-edit cells shipped three times. The shell keeps the `aria-live` region outside the Popover/Drawer, since closing is what commits and it was unmounting the node that would announce the result; `variant="sheet"` took a `footer` slot, which let the signal control drop the overlay it only kept for one button. Two bugs the extraction made visible: a revert issued while a different payload was in flight was dropped silently, and `change()` never cleared a past failure, which outranks everything the live region would otherwise say.
91 lines
3 KiB
TypeScript
91 lines
3 KiB
TypeScript
import 'server-only'
|
|
|
|
import config from '@payload-config'
|
|
import { getPayload, type Payload } from 'payload'
|
|
|
|
import type { CampaignUser } from '@/payload-types'
|
|
import { isCampaignStaff, isCampaignUnrestricted } from '@/utilities/campaignAccess'
|
|
import { getCampaignUser } from '@/utilities/campaignAuth'
|
|
import { CAMPAIGN_AUTH_REQUIRED_MESSAGE } from '@/utilities/campaignFormActionError'
|
|
import type { PayloadTransactionRequest } from '@/utilities/payloadTransaction'
|
|
|
|
export type CampaignActionContext = {
|
|
payload: Payload
|
|
actor: CampaignUser
|
|
}
|
|
|
|
type CampaignUserGetter = () => Promise<CampaignUser | null>
|
|
type CampaignActorPayload = Pick<Payload, 'findByID'>
|
|
|
|
export const requireCampaignUser = async (
|
|
getUser: CampaignUserGetter = getCampaignUser,
|
|
): Promise<CampaignUser> => {
|
|
const actor = await getUser()
|
|
if (!actor) throw new Error(CAMPAIGN_AUTH_REQUIRED_MESSAGE)
|
|
return actor
|
|
}
|
|
|
|
export const reloadCampaignActor = (
|
|
payload: CampaignActorPayload,
|
|
actor: CampaignUser,
|
|
req?: PayloadTransactionRequest,
|
|
): Promise<CampaignUser> =>
|
|
payload.findByID({
|
|
collection: 'campaignUser',
|
|
id: actor.id,
|
|
depth: 0,
|
|
// Intentional admin bypass: this is the trusted fresh-role reload used before authorization.
|
|
overrideAccess: true,
|
|
req,
|
|
})
|
|
|
|
/**
|
|
* Reloads the actor's fresh role and asserts they are campaign staff
|
|
* (coordinator, advisor, or candidate — matching collection-level access via
|
|
* `isCampaignStaff`). Throws `errorMessage` otherwise.
|
|
*/
|
|
export const reloadStaffActor = async (
|
|
payload: CampaignActorPayload,
|
|
actor: CampaignUser,
|
|
errorMessage: string,
|
|
req?: PayloadTransactionRequest,
|
|
): Promise<CampaignUser> => {
|
|
const currentActor = await reloadCampaignActor(payload, actor, req)
|
|
if (!isCampaignStaff(currentActor)) throw new Error(errorMessage)
|
|
return currentActor
|
|
}
|
|
|
|
/**
|
|
* Reloads the actor's fresh role and asserts they are the coordinator.
|
|
* Throws `errorMessage` otherwise.
|
|
*/
|
|
export const reloadCoordinatorActor = async (
|
|
payload: CampaignActorPayload,
|
|
actor: CampaignUser,
|
|
errorMessage: string,
|
|
req?: PayloadTransactionRequest,
|
|
): Promise<CampaignUser> => {
|
|
const currentActor = await reloadCampaignActor(payload, actor, req)
|
|
if (currentActor.role !== 'coordinator') throw new Error(errorMessage)
|
|
return currentActor
|
|
}
|
|
|
|
/**
|
|
* Reloads the actor's fresh role and asserts they are unrestricted
|
|
* (coordinator or candidate). Throws `errorMessage` otherwise.
|
|
*/
|
|
export const reloadUnrestrictedActor = async (
|
|
payload: CampaignActorPayload,
|
|
actor: CampaignUser,
|
|
errorMessage: string,
|
|
req?: PayloadTransactionRequest,
|
|
): Promise<CampaignUser> => {
|
|
const currentActor = await reloadCampaignActor(payload, actor, req)
|
|
if (!isCampaignUnrestricted(currentActor)) throw new Error(errorMessage)
|
|
return currentActor
|
|
}
|
|
|
|
export const getCampaignActionContext = async (): Promise<CampaignActionContext> => {
|
|
const [payload, actor] = await Promise.all([getPayload({ config }), requireCampaignUser()])
|
|
return { payload, actor }
|
|
}
|