Rest API should not return id
s for read denied records
#9524
Replies: 3 comments 6 replies
-
For anyone else running into this issue, here is function and type that you can use with the generated types to remove numbers from the array responses. This will remove all numbers from all array fields, so it should not be used if you know your query depth will not be deep enough, or you expect your response to include an array of numbers. type RemoveNumberFromMixedArrays<T> = T extends (number | infer U)[] // Check if T is an array of number or some other type
? U[] // Transform it to an array of the other type, removing `number`
: T extends object // If T is an object, recurse into its fields
? { [K in keyof T]: RemoveNumberFromMixedArrays<T[K]> }
: T; // For all other types, leave them as-is
function removeNumbersFromMixedArrays<T>(
value: T,
): RemoveNumberFromMixedArrays<T> {
// recursively remove numbers from arrays
if (Array.isArray(value)) {
return value.filter(
(v) => typeof v !== "number",
) as RemoveNumberFromMixedArrays<T>;
}
// recursively filter the values of an object
if (typeof value === "object" && value !== null) {
return Object.fromEntries(
Object.entries(value).map(([key, value]) => [
key,
removeNumbersFromMixedArrays(value),
]),
) as RemoveNumberFromMixedArrays<T>;
}
return value as RemoveNumberFromMixedArrays<T>;
} an example usage for me, using tanstack query, with a global useSuspenseQuery({
queryKey: ["homepage"],
queryFn: async () => {
const res = await fetch(
`${process.env.PAYLOAD_URL}/api/globals/homepage?depth=1`,
);
if (res.ok) {
const body = (await res.json()) as Homepage;
return removeNumbersFromMixedArrays(body);
} else {
throw (await res.json()) as { errors: { message: string }[] };
}
},
}); |
Beta Was this translation helpful? Give feedback.
-
I appreciate the code examples, but you are still making the fetch and removing the data on the client side, so this is not really any more secure if that is the goal. A little context for why Payload works this the way it does, serving the You can always add read access to the relationship field itself in order to enforce that relationship IDs should not come through by config. That would be the payload preferred way to do this to not result in any data loss when saving content. |
Beta Was this translation helpful? Give feedback.
-
Here is a better type for recursively removing export type Deep<T> = unknown extends T
? T // don't modify T if it's any or unknown; doing so will cause infinite recursion
: number extends T // if T can be a number
? Exclude<T, number | undefined | null> extends never // and can only be a number, null, or undefined
? T // Then don't modify it. This number does not represent an ID from payload.
: Deep<Exclude<T, number>> // Otherwise, remove the number (ID) option and recurse
: T extends object // if T can't be a number and is an object,
? { [K in keyof T]: Deep<T[K]> } // recurse
: T extends (infer U)[] // if T can't be a number and is an array
? Deep<U>[] // recurse
: T; // otherwise, return T Usage (assuming HomePage has 1 nested level of relationships only. API depth would have to be set explicitly for other queries): import { useQuery } from "@tanstack/react-query";
import { HomePage } from "path/to/payload-types";
import type { Deep } from "path/to/deep";
/**
* Gets the homepage global from the API
*/
export default function useHomepage() {
return useQuery<Deep<HomePage>>({
queryKey: ["homepage"],
queryFn: async () => {
const res = await fetch(
`${process.env.PAYLOAD_URL}/api/globals/homePage`,
);
if (res.ok) {
return await res.json();
} else {
throw {
...(await res.json()),
code: res.status,
};
}
},
});
} |
Beta Was this translation helpful? Give feedback.
-
When using the Rest API, relationship fields can return a mix of numbers and objects when the field contains records to which the user doesn't have read access.
To me, most of the time there is no use for an id of a record that the user cannot access (exceptions exist). Additionally, this could be a leak of sensitive data, depending on what the ID is used for.
All in all, this behavior results in
I would propose that the Rest API should return only the fields to which the user has read access by default, with an option in configuration to opt-in to the current behavior (possibly
allowUnreadableIds
on the relationship field as a config option would cover it?)Beta Was this translation helpful? Give feedback.
All reactions