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'
  }
})

See also