Database Permissions
Restura supports both table-level and column-level access control using roles and scopes.
Table-Level Permissions
Section titled “Table-Level Permissions”Control which users can access the entire table.
Properties
Section titled “Properties”| Property | Description |
|---|---|
| Roles | User roles that can query this table |
| Scopes | OAuth-style scopes for table access |
How It Works
Section titled “How It Works”If a table has roles/scopes defined:
- Users without the required role/scope cannot query the table
- An error is thrown: “You do not have permission to access this table”
- The query is rejected before any data is accessed
If a table has no roles/scopes:
- The table is considered public
- Any authenticated (or unauthenticated) user can access it
- Column-level permissions still apply
Column-Level Permissions
Section titled “Column-Level Permissions”Control which users can see specific columns in responses.
Properties
Section titled “Properties”| Property | Description |
|---|---|
| Roles | User roles that can see this column in responses |
| Scopes | OAuth-style scopes for column access |
How It Works
Section titled “How It Works”Columns with roles/scopes:
- Only included in responses if the user has the required access
- Silently excluded if the user lacks permission
- Cannot be used in WHERE clauses by unauthorized users
Columns without roles/scopes:
- Included for all users who can access the table
- Can be used in WHERE clauses by anyone
If a user doesn’t have access to any columns:
- The query fails with a permission error
- At least one column must be accessible
Permission Examples
Section titled “Permission Examples”Public Table
Section titled “Public Table”Table: productRoles: []Scopes: []Anyone can access products. All columns are visible to everyone.
Admin-Only Table
Section titled “Admin-Only Table”Table: auditLogRoles: ['ADMIN']Scopes: []Only admins can access the audit log table.
Scope-Based Access
Section titled “Scope-Based Access”Table: userScopes: ['read:users']Only users with the read:users scope can access the user table.
Hide Sensitive Column
Section titled “Hide Sensitive Column”Table: userRoles: []Scopes: []
Column: socialSecurityNumberRoles: ['ADMIN', 'HR']Scopes: []Everyone can access the user table, but only admins and HR can see social security numbers.
Multiple Permission Levels
Section titled “Multiple Permission Levels”Table: orderRoles: []Scopes: []
Column: customerEmailRoles: ['ADMIN', 'MANAGER']Scopes: []
Column: internalNotesRoles: ['ADMIN']Scopes: []- Everyone can see orders
- Admins and managers can see customer emails
- Only admins can see internal notes
Common Permission Patterns
Section titled “Common Permission Patterns”Multi-Tenant Data Isolation
Section titled “Multi-Tenant Data Isolation”Use global parameters to filter data by company:
Table: orderRoles: []Scopes: []
Endpoint: GET /api/v1/ordersWhere: order.companyId = #companyIdUsers can only see orders from their own company, enforced at the query level.
Role-Based Column Access
Section titled “Role-Based Column Access”Table: employeeRoles: []Scopes: []
Columns: - firstName, lastName: [] (public) - email: ['ADMIN', 'HR'] (restricted) - salary: ['ADMIN', 'HR'] (restricted) - socialSecurityNumber: ['ADMIN', 'HR'] (restricted) - performanceReview: ['ADMIN', 'MANAGER'] (restricted)Different roles see different levels of employee information.
Hierarchical Permissions
Section titled “Hierarchical Permissions”Table: reportRoles: ['ADMIN', 'MANAGER', 'ANALYST']Scopes: []
Column: financialDataRoles: ['ADMIN', 'MANAGER']Scopes: []
Column: confidentialNotesRoles: ['ADMIN']Scopes: []- Analysts can see reports but not financial data
- Managers can see financial data but not confidential notes
- Admins can see everything
Scope-Based API Access
Section titled “Scope-Based API Access”Table: userScopes: ['read:users']
Column: emailScopes: ['read:users:email']
Column: phoneScopes: ['read:users:phone']
Column: addressScopes: ['read:users:address']Fine-grained OAuth scopes control what data third-party apps can access.
Permission Enforcement
Section titled “Permission Enforcement”Query-Time Filtering
Section titled “Query-Time Filtering”Permissions are enforced when queries are executed:
- Table-level check – Verify user has access to the table
- Column filtering – Remove unauthorized columns from SELECT
- WHERE clause validation – Ensure user can filter by specified columns
- Response filtering – Remove unauthorized columns from results
Example Query Flow
Section titled “Example Query Flow”User: Manager with roles ['MANAGER']
Query: GET /api/v1/users?fields=firstName,email,salary
Table permissions: [] (public)
Column permissions:
firstName:[](public)email:['ADMIN', 'MANAGER'](restricted)salary:['ADMIN', 'HR'](restricted)
Result:
{ "data": [ { "firstName": "John", "email": "john@example.com" // salary excluded - user lacks permission } ]}Combining with Endpoint Permissions
Section titled “Combining with Endpoint Permissions”Endpoint permissions and database permissions work together:
Endpoint Permission
Section titled “Endpoint Permission”Route: GET /api/v1/usersRoles: ['ADMIN', 'MANAGER']Only admins and managers can call this endpoint.
Database Permission
Section titled “Database Permission”Table: userRoles: []
Column: salaryRoles: ['ADMIN']Managers can call the endpoint, but only admins can see salary data.
Result
Section titled “Result”- Admins see all user data including salaries
- Managers see all user data except salaries
- Other users get a 403 error before querying the database
Learn more about endpoint permissions.
Best Practices
Section titled “Best Practices”Start with Least Privilege
Section titled “Start with Least Privilege”- Grant minimum necessary permissions
- Add permissions as needed, don’t start with full access
- Use table-level permissions for sensitive tables
- Use column-level permissions for PII
Choose Between Roles or Scopes
Section titled “Choose Between Roles or Scopes”Roles and scopes are mutually exclusive - you should use one or the other for a given table or column, not both. When evaluating permissions, Restura checks roles first. If no roles are defined, it falls back to checking scopes.
Use Roles for Internal Users
Section titled “Use Roles for Internal Users”Roles: ['ADMIN', 'MANAGER', 'USER']Roles are good for internal users with fixed permission levels.
Use Scopes for API Access
Section titled “Use Scopes for API Access”Scopes: ['read:users', 'write:users', 'read:orders']Scopes are good for third-party apps and fine-grained API access.
Document Permission Requirements
Section titled “Document Permission Requirements”Add comments explaining why permissions are set:
Table: payrollRoles: ['ADMIN', 'HR']Comment: "Contains sensitive salary and tax information"
Column: socialSecurityNumberRoles: ['ADMIN', 'HR']Comment: "PII - restricted to authorized personnel only"Test Permission Scenarios
Section titled “Test Permission Scenarios”Test with different user roles to ensure permissions work as expected:
// Test as adminconst adminResponse = await fetch('/api/v1/users', { headers: { Authorization: `Bearer ${adminToken}` }});// Should see all columns
// Test as managerconst managerResponse = await fetch('/api/v1/users', { headers: { Authorization: `Bearer ${managerToken}` }});// Should see limited columns
// Test as guestconst guestResponse = await fetch('/api/v1/users', { headers: { Authorization: `Bearer ${guestToken}` }});// Should get 403 errorAudit Permission Changes
Section titled “Audit Permission Changes”Log when permissions are modified:
await auditLog.record('permission_changed', { table: 'user', column: 'salary', oldRoles: ['ADMIN'], newRoles: ['ADMIN', 'HR'], changedBy: currentUser.id, timestamp: new Date()});Security Considerations
Section titled “Security Considerations”Never Trust Client Input
Section titled “Never Trust Client Input”Always validate permissions server-side:
// Bad: Client specifies which columns to returnconst columns = req.query.columns; // Could include unauthorized columnsconst users = await db.select(columns).from('user');
// Good: Server enforces column permissionsconst users = await db.query.user.findMany({ // Restura automatically filters columns based on user permissions});Use Global Parameters for Data Isolation
Section titled “Use Global Parameters for Data Isolation”-- Ensure users only see their own company's dataWHERE order.companyId = #companyIdThis prevents users from accessing other companies’ data by manipulating IDs.
Protect Sensitive Columns
Section titled “Protect Sensitive Columns”For columns that should never be exposed via the API (like password hashes), you have two options:
Option 1: Don’t include them in your schema (Recommended)
If a column exists in your database but is not defined in your Restura schema, it will never be accessible through the API.
Option 2: Use a restricted role that’s never assigned
Table: userRoles: []Scopes: []
Column: passwordHashRoles: ['RESTRICTED']Comment: "Never expose - RESTRICTED role should never be assigned to any user"Create a RESTRICTED role in your schema that you never assign to any actual users. This effectively blocks all access to the column.
Implement Row-Level Security
Section titled “Implement Row-Level Security”Combine permissions with WHERE clauses:
Table: documentRoles: []
Endpoint: GET /api/v1/documentsWhere: document.ownerId = #userId OR document.isPublic = trueUsers can only see their own documents or public documents.
Error Messages
Section titled “Error Messages”Table Permission Denied
Section titled “Table Permission Denied”{ "error": { "code": "FORBIDDEN", "message": "You do not have permission to access this table" }}Column Permission Denied
Section titled “Column Permission Denied”Columns are silently excluded from results. No error is thrown unless the user has no access to any columns.
No Accessible Columns
Section titled “No Accessible Columns”{ "error": { "code": "FORBIDDEN", "message": "You do not have permission to access any columns in this table" }}