> ## Documentation Index
> Fetch the complete documentation index at: https://docs-terra.withunify.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorization

> Role-based access control and permission checking in Terra

# Authorization

> Terra uses fail-secure RBAC: when in doubt, deny access.

## Role Hierarchy

| Role          | Dashboard | Forms                | Submissions | Settings | Users  |
| ------------- | --------- | -------------------- | ----------- | -------- | ------ |
| `super_admin` | Full      | All                  | All         | All      | Manage |
| `admin`       | Full      | All                  | All         | View     | View   |
| `editor`      | Limited   | Assigned (edit)      | Assigned    | —        | —      |
| `viewer`      | Limited   | Assigned (read-only) | Assigned    | —        | —      |
| `user`        | Limited   | Assigned             | Assigned    | —        | —      |
| `applicant`   | —         | Submit               | Own only    | —        | —      |

`editor` and `viewer` are **scoped roles** — they only see forms explicitly assigned to them by an admin. See [Scoped Access](/core-systems/identity/scoped-access) for details.

## Permission Checks

Every server action checks permissions:

```typescript theme={null}
// src/app/actions/team.ts

export async function requireAdmin() {
  const session = await getSession();
  if (!session) throw new Error("Unauthorized");

  const { data: user } = await supabaseAdmin
    .from("user_profiles")
    .select("role")
    .eq("workos_user_id", session.user.id)
    .single();

  if (!user || !["admin", "super_admin"].includes(user.role)) {
    throw new Error("Forbidden");
  }

  return user;
}
```

## Fail-Secure Pattern

```typescript theme={null}
export async function requireFormAccess(formId: string) {
  try {
    const session = await getSession();
    const user = await getUserWithRole(session.user.id);

    // Super admins see everything
    if (user.role === "super_admin") return user;

    // Check form-level access
    const hasAccess = await checkFormAccess(formId, user.id);
    if (!hasAccess) throw new Error("No access to form");

    return user;
  } catch (error) {
    // ANY error = no access
    throw new Error("Unauthorized");
  }
}
```

**Key principle:** If permission check fails for any reason (database error, missing user, etc.), deny access. Never fail open.

## Folder-Based Access

Users can have access to specific folders (workspaces):

```sql theme={null}
SELECT f.id
FROM forms f
JOIN folder_members fm ON fm.folder_id = f.folder_id
WHERE fm.user_id = $1 AND f.id = $2
```

***

<CardGroup cols={2}>
  <Card title="Scoped Access" icon="user-lock" href="/core-systems/identity/scoped-access">
    Editor and viewer form assignments
  </Card>

  <Card title="Authentication" icon="key" href="/core-systems/identity/authentication">
    Login flow and sessions
  </Card>

  <Card title="Multi-Tenancy" icon="building" href="/core-systems/identity/multi-tenancy">
    Organization structure
  </Card>
</CardGroup>
