Thanks for the additional details.
The history_read
permission is implemented in v10, so we need to get the documentation updated.
Importantly, the document passed to the history_read
predicate is the latest, active version, not the version at the snapshot time. This is intentional. If we changed the reads to use the at
time it would open you up to issues, for example, if you wanted to remove access to some data, then someone might be able to read information in the past when the current state says they shouldn’t. By always using the latest version, we ensure that any permissions rely on the latest changes, and you don’t have any such security loop holes.
If you delete a document, there is no active version, and the predicate will have no document to work with.
workarounds
The simplest way around this may be to create a readDeletedObject
Function with permissions to read all history, then check for permissions within the Function and abort if permission check fails.
Predicates can make additional database reads. You may be able to work around the limitation by reading the latest version before deletion, and using that for permissions. Similar to the Function, but more built into the Role.
These would require the v4 Events
function, which is not implemented yet in v10. use before: null
to get the last page of events.
Paginate(Events(Ref(Collection("things"), "1234")), { before: null })
{
after: {
ts: 9223372036854776000,
action: "add"
},
data: [
{
ts: 1708550326776000,
action: "create",
document: Ref(Collection("things"), "1234"),
data: {
value: 3,
updated: true
}
},
{
ts: 1710947028140000,
action: "update",
document: Ref(Collection("things"), "1234"),
data: null
},
{
ts: 1710947073500000,
action: "update",
document: Ref(Collection("things"), "1234"),
data: {
value: 5
}
},
{
ts: 1710949299303000,
action: "delete",
document: Ref(Collection("things"), "1234"),
data: null
}
]
}
Let(
{
id: "1234",
ref: Ref(Collection("things"), Var("id")),
recentEvents: Select("data", Paginate(Events(Var("ref")), { size: 2, before: null })),
removeDeletes: Filter(Var("recentEvents"), e => Not(Equals("delete", Select("action", e)))),
latestNonDeleteEvent: Select(Add(-1, Count(Var("removeDeletes"))), Var("removeDeletes")),
ts: Select("ts", Var("latestNonDeleteEvent"))
},
At(Var("ts"), Get(Var("ref")))
)
{
ref: Ref(Collection("things"), "1234"),
ts: 1710947073500000,
data: {
value: 5,
updated: true
}
}
Or maybe you could have a kind of “ObjectsTrash” collection that you would send documents to when they are deleted, and you could read/restore from there.