Shared access between users
Grant other users access to your data using a shares table and existence-based permissions.
After user-owned data, the next pattern you're likely to need is sharing. This recipe shows how to let one user grant another access to specific rows using a shares table.
Schema
Add a todoShares table that records which user has access to which todo.
const schema = {
todos: s.table({
title: s.string(),
done: s.boolean(),
}),
todoShares: s.table({
todoId: s.ref("todos"),
user_id: s.string(),
can_edit: s.boolean(),
}),
};
type AppSchema = s.Schema<typeof schema>;
export const app: s.App<AppSchema> = s.defineApp(schema);Permissions
The creator can do everything. Share recipients get read access, and optionally edit access via can_edit.
s.definePermissions(app, ({ policy, anyOf, session }) => {
policy.todos.allowRead.where((todo) =>
anyOf([
{ $createdBy: session.user_id },
policy.todoShares.exists.where({
todoId: todo.id,
user_id: session.user_id,
}),
]),
);
policy.todos.allowInsert.always();
policy.todos.allowUpdate.where((todo) =>
anyOf([
{ $createdBy: session.user_id },
policy.todoShares.exists.where({
todoId: todo.id,
user_id: session.user_id,
can_edit: true,
}),
]),
);
policy.todos.allowDelete.where({ $createdBy: session.user_id });
// Only the todo creator can manage shares
policy.todoShares.allowInsert.where((share) =>
policy.todos.exists.where({
id: share.todoId,
$createdBy: session.user_id,
}),
);
policy.todoShares.allowRead.where({ user_id: session.user_id });
policy.todoShares.allowDelete.where((share) =>
policy.todos.exists.where({
id: share.todoId,
$createdBy: session.user_id,
}),
);
});See Permissions for more on exists.where, anyOf, and allOf.
Granting access
To share a todo, insert a row into todoShares.
export function shareTodo(db: ReturnType<typeof useDb>, todoId: string, recipientUserId: string) {
db.insert(app.todoShares, {
todoId,
user_id: recipientUserId,
can_edit: false,
});
}Querying shared items
export function SharedWithMe() {
const session = useSession();
const shares = useAll(
app.todoShares.where({ user_id: session!.user_id }).include({ todo: true }),
);
if (!shares) return <p>Loading…</p>;
return (
<ul>
{shares.map((share) => (
<li key={share.id}>
{share.todo.title}
{share.can_edit ? " (can edit)" : " (read-only)"}
</li>
))}
</ul>
);
}Revoking access
Delete the share row to revoke access. The server will stop syncing the todo to the former recipient.
export function unshareTodo(db: ReturnType<typeof useDb>, shareId: string) {
db.delete(app.todoShares, shareId);
}