or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

authorization.mddocs/guides/

Authorization Guide

This guide shows you how to implement role-based access control using the CASL-based authorization system. For detailed API documentation, see Authorization API.

Overview

Lightdash uses CASL (an isomorphic authorization library) for permissions:

  • Organization roles - Admin, member, viewer
  • Project roles - Admin, developer, editor, interactive viewer, viewer
  • Custom roles - Define your own permission sets
  • Scopes - Granular permissions for specific features
  • Service accounts - Machine-to-machine authentication

Quick Start

Check Basic Permissions

import { defineUserAbility } from "@lightdash/common";

const ability = defineUserAbility(sessionUser, projectProfiles);

// Check permissions
if (ability.can("view", "Dashboard")) {
  console.log("User can view dashboards");
}

if (ability.can("create", "SavedChart")) {
  console.log("User can create charts");
}

if (ability.can("manage", "Organization")) {
  console.log("User is organization admin");
}

Defining User Abilities

Organization-Level Permissions

import {
  defineUserAbility,
  OrganizationMemberRole,
  type LightdashUser,
} from "@lightdash/common";

const adminUser: LightdashUser = {
  userUuid: "user-123",
  organizationUuid: "org-456",
  role: OrganizationMemberRole.ADMIN,
  // ... other properties
};

const ability = defineUserAbility(adminUser, []);

// Organization admins have elevated privileges
console.log(ability.can("manage", "Organization"));        // true
console.log(ability.can("manage", "OrganizationMemberProfile")); // true
console.log(ability.can("manage", "Project"));             // true

Project-Level Permissions

import {
  defineUserAbility,
  ProjectMemberRole,
  type ProjectMemberProfile,
} from "@lightdash/common";

const projectProfiles: ProjectMemberProfile[] = [
  {
    projectUuid: "project-1",
    role: ProjectMemberRole.EDITOR,
    userUuid: "user-123",
  },
  {
    projectUuid: "project-2",
    role: ProjectMemberRole.VIEWER,
    userUuid: "user-123",
  },
];

const ability = defineUserAbility(user, projectProfiles);

// User has different abilities in different projects

Understanding Roles

Organization Roles

enum OrganizationMemberRole {
  ADMIN = "admin",
  MEMBER = "member",
  VIEWER = "viewer",
}
  • Admin - Full organization control
  • Member - Can create projects and content
  • Viewer - Read-only access

Project Roles

enum ProjectMemberRole {
  ADMIN = "admin",
  DEVELOPER = "developer",
  EDITOR = "editor",
  INTERACTIVE_VIEWER = "interactive_viewer",
  VIEWER = "viewer",
}

Role Hierarchy (most to least permissions):

  1. Admin - Full project control
  2. Developer - Can compile projects and manage SQL
  3. Editor - Can create and edit content
  4. Interactive Viewer - Can explore and create ad-hoc queries
  5. Viewer - Read-only access

Get Role Scopes

import { getAllScopesForRole, ProjectMemberRole } from "@lightdash/common";

// Get all scopes for a role
const editorScopes = getAllScopesForRole(ProjectMemberRole.EDITOR);
console.log(`Editor has ${editorScopes.length} scopes`);

// Check if role has specific scope
if (editorScopes.includes("manage:Dashboard")) {
  console.log("Editors can manage dashboards");
}

Permission Checks

Basic Permission Checks

const ability = defineUserAbility(user, projectProfiles);

// Simple checks
ability.can("view", "Dashboard")           // Can view dashboards?
ability.can("create", "SavedChart")        // Can create charts?
ability.can("update", "Space")             // Can update spaces?
ability.can("delete", "Project")           // Can delete projects?
ability.can("export", "ExportCsv")         // Can export to CSV?
ability.can("manage", "Organization")      // Can manage organization?

Resource-Specific Checks

Check permissions on specific resources:

// Check if user can update a specific chart
if (ability.can("update", {
  subject: "SavedChart",
  organizationUuid: "org-456",
  projectUuid: "project-1",
  access: [],
})) {
  console.log("User can update this chart");
}

// Check project access
if (ability.can("view", {
  type: "Project",
  uuid: "project-1",
})) {
  console.log("User can view project-1");
}

UI Conditional Rendering

Show/hide UI elements based on permissions:

function DashboardActions({ ability }) {
  return (
    <div>
      {ability.can("create", "Dashboard") && (
        <button>Create Dashboard</button>
      )}

      {ability.can("update", "Dashboard") && (
        <button>Edit Dashboard</button>
      )}

      {ability.can("delete", "Dashboard") && (
        <button>Delete Dashboard</button>
      )}

      {ability.can("export", "DashboardPdf") && (
        <button>Export to PDF</button>
      )}
    </div>
  );
}

Custom Roles and Scopes

Define Custom Roles

import { defineUserAbility } from "@lightdash/common";

// Define custom role scopes
const customRoleScopes = {
  "custom-role-uuid": [
    "view:Project",
    "view:Dashboard",
    "view:SavedChart",
    "export:DashboardCsv",
    "view:UnderlyingData",
  ],
};

const ability = defineUserAbility(
  user,
  [{
    projectUuid: "project-1",
    role: "custom-role-uuid" as ProjectMemberRole,
    userUuid: user.userUuid,
    roleUuid: "custom-role-uuid",
  }],
  customRoleScopes
);

// User has custom role abilities

Understanding Scopes

Scopes are granular permissions in the format action:Subject:

// View permissions
"view:Dashboard"
"view:SavedChart"
"view:Project"

// Manage permissions
"manage:Dashboard"
"manage:SavedChart"
"manage:Space"

// Export permissions
"export:DashboardCsv"
"export:DashboardPdf"
"export:DashboardImage"

// Data access
"view:UnderlyingData"
"manage:SqlRunner"
"manage:CustomSql"

Scope Modifiers

Some scopes have modifiers for conditional access:

// Space management
"manage:Space"           // Manage all spaces
"manage:Space@public"    // Manage public spaces only
"manage:Space@assigned"  // Manage assigned spaces only

// Self-access only
"view:Job@self"          // View own jobs only
"delete:Project@self"    // Delete own projects only

Service Accounts

Service accounts use scope-based permissions for API access.

Service Account Scopes

import {
  applyServiceAccountAbilities,
  ServiceAccountScope,
  getUserAbilityBuilder,
} from "@lightdash/common";

const builder = getUserAbilityBuilder({
  user: serviceAccount,
  projectProfiles: [],
  permissionsConfig: { pat: { enabled: false, allowedOrgRoles: [] } },
});

// Read-only access
applyServiceAccountAbilities({
  organizationUuid: "org-uuid",
  builder,
  scopes: [ServiceAccountScope.ORG_READ],
});

const ability = builder.build();
console.log(ability.can("view", "Dashboard"));   // true
console.log(ability.can("manage", "Dashboard")); // false

Service Account Scope Levels

enum ServiceAccountScope {
  ORG_READ = "org:read",       // View-only access
  ORG_EDIT = "org:edit",       // Create/edit in public spaces
  ORG_ADMIN = "org:admin",     // Full admin access
  SCIM_MANAGE = "scim:manage", // User provisioning
}

Embedded Content Authorization

JWT-Based Access

Control access to embedded dashboards and charts:

import {
  applyEmbeddedAbility,
  getUserAbilityBuilder,
  JWT_HEADER_NAME,
} from "@lightdash/common";

const embedUser = {
  content: {
    type: "dashboard",
    canExplore: true,
    canViewUnderlyingData: false,
    canExportCsv: true,
    canExportImages: true,
    canExportPagePdf: true,
    canDateZoom: true,
  },
};

const builder = getUserAbilityBuilder({
  user: embedUser,
  projectProfiles: [],
  permissionsConfig: { pat: { enabled: false, allowedOrgRoles: [] } },
});

applyEmbeddedAbility(
  embedUser,
  dashboardContent,
  embedConfig,
  "customer-123",
  builder
);

const ability = builder.build();

Checking Multiple Permissions

Batch permission checks for UI:

import { defineUserAbility } from "@lightdash/common";

const ability = defineUserAbility(user, projectProfiles);

// Check multiple permissions at once
const permissions = {
  // Content permissions
  canViewDashboards: ability.can("view", "Dashboard"),
  canCreateDashboards: ability.can("create", "Dashboard"),
  canUpdateDashboards: ability.can("update", "Dashboard"),
  canDeleteDashboards: ability.can("delete", "Dashboard"),

  // Export permissions
  canExportCsv: ability.can("export", "ExportCsv"),
  canExportPdf: ability.can("export", "DashboardPdf"),
  canExportImages: ability.can("export", "DashboardImage"),

  // Data access
  canViewUnderlyingData: ability.can("view", "UnderlyingData"),
  canRunCustomSql: ability.can("manage", "CustomSql"),

  // Admin permissions
  canManageSpaces: ability.can("manage", "Space"),
  canManageProject: ability.can("manage", "Project"),
  canManageOrg: ability.can("manage", "Organization"),
};

console.log("User permissions:", permissions);

Common Permission Patterns

Content Creation Flow

// 1. Check if user can create
if (!ability.can("create", "Dashboard")) {
  throw new Error("Unauthorized: Cannot create dashboard");
}

// 2. Create the resource
const dashboard = await createDashboard(data);

// 3. Check if user can view the created resource
if (ability.can("view", dashboard)) {
  return dashboard;
}

Content Update Flow

// 1. Load resource
const dashboard = await getDashboard(uuid);

// 2. Check update permission
if (!ability.can("update", dashboard)) {
  throw new Error("Unauthorized: Cannot update dashboard");
}

// 3. Update resource
await updateDashboard(uuid, changes);

Content Deletion Flow

// 1. Check delete permission
if (!ability.can("delete", {
  subject: "Dashboard",
  uuid: dashboardUuid,
})) {
  throw new Error("Unauthorized: Cannot delete dashboard");
}

// 2. Delete resource
await deleteDashboard(dashboardUuid);

Tips and Best Practices

1. Check Permissions Early

Always check permissions before performing operations:

// Good
if (!ability.can("update", "Dashboard")) {
  return res.status(403).json({ error: "Forbidden" });
}
// ... proceed with update

// Avoid
// ... perform update, then check permissions

2. Use Specific Subjects

Be specific about what you're checking:

// Good
ability.can("update", "Dashboard")
ability.can("export", "DashboardCsv")

// Less clear
ability.can("manage", "Dashboard") // Too broad

3. Cache Abilities

Create abilities once per request:

// In middleware
req.ability = defineUserAbility(req.user, req.projectProfiles);

// In route handlers
if (req.ability.can("view", "Dashboard")) {
  // ...
}

4. Handle Missing Permissions Gracefully

Provide clear error messages:

if (!ability.can("create", "Dashboard")) {
  throw new Error(
    "You don't have permission to create dashboards. " +
    "Contact your organization admin to request access."
  );
}

Next Steps