Permissions
Basics
Permissions are implemented by using throw Abort()
and return
:
// TodoItem.telefunc.ts
// Environment: server
export { onTextChange }
import { getContext, Abort } from 'telefunc'
function onTextChange(id: string, text: string) {
const { user } = getContext()
if (!user) {
// We return `notLoggedIn: true` so that the frontend can redirect the user to the login page
return { notLoggedIn: true }
}
const todoItem = await Todo.findOne({ id })
if (!todoItem) {
// `throw Abort()` corresponds to "403 Forbidden" of classical APIs
throw Abort()
}
// We can easily programmatically implement advanced permissions such
// as "only allow the author or admins to modify a to-do item".
if (todoItem.authorId !== user.id && !user.isAdmin) {
throw Abort()
}
await todoItem.update({ text })
}
In general, we use throw Abort()
upon permission denials but, sometimes, the frontend needs to know why the the telefunction call failed:
in this example we return { notLoggedIn: true }
instead of throw Abort()
so that the frontend can perform a redirection:
// TodoItem.tsx
// Environment: client
import { onTextChange } from './TodoItem.telefunc'
function onChange(id: string, text: string) {
const res = await onTextChange(id, text)
if (res?.notLoggedIn) {
// Redirect user to login page
window.location.href = '/login'
}
}
function TodoItem({ id, text }: { id: string; text: string }) {
return <input input="text" value={text} onChange={(ev) => onChange(id, ev.target.value)} />
}
getContext()
wrapping
To implement permission logic once and re-use it, we can define a getContext()
wrapper:
// components/TodoItem.telefunc.ts
// Environment: server
export { onTextChange }
import { getUser } from '../auth/getUser'
function onTextChange(id: string, text: string) {
const user = getUser()
/* ... */
}
// auth/getUser.ts
// Environment: server
// Note that getUser() isn't a telefunction: it's a wrapper around getContext()
export { getUser }
import { getContext, Abort } from 'telefunc'
function getUser() {
const { user } = getContext()
if (!user) {
throw Abort({ notLoggedIn: true })
}
return user
}
// Environment: client
import { onAbort } from 'telefunc/client'
onAbort(err => {
if (err.abortValue.notLoggedIn) {
// Redirect user to login page
window.location.href = '/login'
}
})