Security Model Guide
Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls
Security Model Guide
Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls.
Table of Contents
- Security Architecture
- Profiles
- Permission Sets
- Role Hierarchy
- Sharing Rules
- Field-Level Security
- Best Practices
Security Architecture
ObjectStack implements a multi-layered security model:
┌─────────────────────────────────────┐
│ Application Visibility │ ← Which apps can users access?
├─────────────────────────────────────┤
│ Object Permissions │ ← CRUD on objects
├─────────────────────────────────────┤
│ Record-Level Security │ ← Which records can be accessed?
│ - Organization-Wide Defaults │
│ - Role Hierarchy │
│ - Sharing Rules │
│ - Manual Sharing │
├─────────────────────────────────────┤
│ Field-Level Security │ ← Read/Write specific fields
└─────────────────────────────────────┘Security Layers
- Object Permissions: Control create, read, update, delete on entire objects
- Record-Level Security: Control access to specific records
- Field-Level Security: Control visibility and editability of fields
- Row-Level Security: Control access based on data values
Profiles
Profiles define a user's baseline permissions.
Profile Structure
import type { Profile } from '@objectstack/spec/security';
export const SalesRepProfile: Profile = {
name: 'sales_rep',
label: 'Sales Representative',
description: 'Standard sales rep permissions',
// Object-level permissions
objectPermissions: {
account: {
create: true,
read: true,
update: true,
delete: false,
viewAll: false, // See all records regardless of owner
modifyAll: false, // Edit all records regardless of owner
},
opportunity: {
create: true,
read: true,
update: true,
delete: false,
viewAll: false,
modifyAll: false,
},
},
// Field-level permissions
fieldPermissions: {
account: {
annual_revenue: { read: true, update: false },
description: { read: true, update: true },
},
},
// Tab visibility
tabVisibility: {
account: 'default', // Default tab
lead: 'default',
opportunity: 'default',
case: 'hidden', // Not visible
product: 'available', // Available but not default
},
// Application access
applicationVisibility: {
crm_example: true,
},
};Object Permission Levels
| Permission | Description |
|---|---|
create | Create new records |
read | View records they own or have access to |
update | Edit records they own or have access to |
delete | Delete records they own or have access to |
viewAll | View ALL records regardless of ownership |
modifyAll | Edit ALL records regardless of ownership |
Standard Profiles
Sales Representative
export const SalesRepProfile: Profile = {
name: 'sales_rep',
objectPermissions: {
lead: { create: true, read: true, update: true, delete: false },
account: { create: true, read: true, update: true, delete: false },
contact: { create: true, read: true, update: true, delete: false },
opportunity: { create: true, read: true, update: true, delete: false },
quote: { create: true, read: true, update: true, delete: false },
product: { create: false, read: true, update: false, delete: false },
},
fieldPermissions: {
account: {
annual_revenue: { read: true, update: false }, // Read-only
},
},
};Sales Manager
export const SalesManagerProfile: Profile = {
name: 'sales_manager',
objectPermissions: {
lead: {
create: true, read: true, update: true, delete: true,
viewAll: true, modifyAll: true
},
account: {
create: true, read: true, update: true, delete: true,
viewAll: true, modifyAll: true
},
// ... full access to sales objects
},
};Service Agent
export const ServiceAgentProfile: Profile = {
name: 'service_agent',
objectPermissions: {
case: { create: true, read: true, update: true, delete: false },
task: { create: true, read: true, update: true, delete: true },
account: { create: false, read: true, update: false, delete: false },
contact: { create: false, read: true, update: true, delete: false },
},
fieldPermissions: {
case: {
is_sla_violated: { read: true, update: false },
resolution_time_hours: { read: true, update: false },
},
},
};Permission Sets
Permission sets extend profile permissions without changing the profile.
import type { PermissionSet } from '@objectstack/spec/security';
export const AdvancedReportingPermissionSet: PermissionSet = {
name: 'advanced_reporting',
label: 'Advanced Reporting',
description: 'Additional permissions for advanced reporting',
objectPermissions: {
opportunity: {
viewAll: true, // Override profile restriction
},
account: {
viewAll: true,
},
},
fieldPermissions: {
opportunity: {
amount: { read: true },
probability: { read: true },
},
},
systemPermissions: {
runReports: true,
exportReports: true,
createDashboards: true,
},
};
export const BulkDataPermissionSet: PermissionSet = {
name: 'bulk_data_access',
label: 'Bulk Data Access',
description: 'Permissions for bulk data operations',
systemPermissions: {
bulkApiEnabled: true,
viewAllData: true,
},
};Assigning Permission Sets
// Assign to user
await assignPermissionSet({
userId: 'user123',
permissionSetName: 'advanced_reporting',
});
// Assign to multiple users
await assignPermissionSetToGroup({
permissionSetName: 'bulk_data_access',
userGroup: 'data_analysts',
});Role Hierarchy
Roles control record-level access through a hierarchy.
export const RoleHierarchy = {
name: 'crm_role_hierarchy',
label: 'CRM Role Hierarchy',
roles: [
// Top level
{
name: 'executive',
label: 'Executive',
parentRole: null,
},
// Sales hierarchy
{
name: 'sales_director',
label: 'Sales Director',
parentRole: 'executive',
},
{
name: 'sales_manager',
label: 'Sales Manager',
parentRole: 'sales_director',
},
{
name: 'sales_rep',
label: 'Sales Representative',
parentRole: 'sales_manager',
},
// Service hierarchy
{
name: 'service_director',
label: 'Service Director',
parentRole: 'executive',
},
{
name: 'service_manager',
label: 'Service Manager',
parentRole: 'service_director',
},
{
name: 'service_agent',
label: 'Service Agent',
parentRole: 'service_manager',
},
],
};How Role Hierarchy Works
Executive
/ \
Sales Director Service Director
| |
Sales Manager Service Manager
| |
Sales Rep Service AgentGrant Access UP: Users see records owned by:
- Themselves
- Their subordinates
- Their subordinates' subordinates (all levels down)
Example: Sales Director sees all records owned by Sales Managers and Sales Reps.
Sharing Rules
Sharing rules extend access beyond the role hierarchy.
Organization-Wide Defaults (OWD)
Set baseline access for all users:
export const OrganizationDefaults = {
lead: {
internalAccess: 'private', // Users see only their own records
externalAccess: 'private',
},
account: {
internalAccess: 'private',
externalAccess: 'private',
},
contact: {
internalAccess: 'controlled_by_parent', // Access controlled by Account
externalAccess: 'private',
},
opportunity: {
internalAccess: 'private',
externalAccess: 'private',
},
campaign: {
internalAccess: 'public_read_only', // All users can read
externalAccess: 'private',
},
product: {
internalAccess: 'public_read_only',
externalAccess: 'private',
},
};Access Levels
| Level | Description |
|---|---|
private | Owner only (+ role hierarchy) |
public_read_only | All users can read |
public_read_write | All users can read and edit |
controlled_by_parent | Controlled by parent object |
Criteria-Based Sharing Rules
Share records based on field criteria:
export const AccountTeamSharingRule: SharingRule = {
name: 'account_team_sharing',
label: 'Share Active Customers with Sales Team',
objectName: 'account',
type: 'criteria_based',
// Criteria: Which records to share
criteria: {
type: { $eq: 'customer' },
is_active: { $eq: true },
},
// Who to share with
sharedWith: {
type: 'role',
roles: ['sales_manager', 'sales_director'],
},
// Access level granted
accessLevel: 'read_write',
// Also share related records
includeRelatedObjects: [
{ objectName: 'contact', accessLevel: 'read_only' },
{ objectName: 'opportunity', accessLevel: 'read_only' },
],
};Owner-Based Sharing Rules
Share based on record owner characteristics:
export const OpportunityOwnerSharingRule: SharingRule = {
name: 'opportunity_owner_sharing',
label: 'Share Opportunities within Same Territory',
objectName: 'opportunity',
type: 'owner_based',
// Share records owned by users in these roles
ownedBy: {
type: 'role',
roles: ['sales_rep'],
},
// Share with users in these roles
sharedWith: {
type: 'role',
roles: ['sales_rep'],
sameTerritory: true, // Only same territory
},
accessLevel: 'read_only',
};Territory-Based Sharing
Share based on geographic territories:
export const TerritorySharingRules = [
{
name: 'north_america_territory',
label: 'North America Territory',
objectName: 'account',
type: 'territory_based',
criteria: {
billing_address: {
country: { $in: ['US', 'CA', 'MX'] },
},
},
sharedWith: {
type: 'territory',
territory: 'north_america',
},
accessLevel: 'read_write',
},
];Field-Level Security
Control visibility and editability of specific fields.
Field Permissions in Profiles
fieldPermissions: {
account: {
// Field-level permissions
annual_revenue: {
read: true, // Can view
update: false // Cannot edit
},
description: {
read: true,
update: true
},
ssn: {
read: false, // Hidden field
update: false
},
},
opportunity: {
amount: { read: true, update: true },
probability: { read: true, update: false },
},
}Hidden vs. Read-Only
// Hidden: Field not visible at all
{ read: false, update: false }
// Read-Only: Field visible but not editable
{ read: true, update: false }
// Editable: Field visible and editable
{ read: true, update: true }Best Practices
1. Profile Design
✅ DO:
- Create minimal profiles (start restrictive, extend with permission sets)
- Use descriptive names
- Document the purpose of each profile
- Assign one profile per user
❌ DON'T:
- Create too many profiles
- Give excessive permissions "just in case"
- Mix unrelated permissions in one profile
2. Permission Sets
✅ DO:
- Use for temporary or special access
- Group related permissions
- Name clearly (e.g., "Advanced Reporting", "Bulk Data Access")
- Remove when no longer needed
❌ DON'T:
- Use as a replacement for profiles
- Grant system-wide permissions unnecessarily
3. Role Hierarchy
✅ DO:
- Mirror your organizational structure
- Keep hierarchies simple
- Document reporting relationships
- Review regularly
❌ DON'T:
- Create overly deep hierarchies
- Mix different org structures
- Grant too much upward access
4. Sharing Rules
✅ DO:
- Start with most restrictive OWD
- Use sharing rules to open up access
- Document business justification
- Test thoroughly
❌ DON'T:
- Set OWD to Public unless necessary
- Create redundant sharing rules
- Grant more access than needed
5. Field-Level Security
✅ DO:
- Protect sensitive data (SSN, salary, etc.)
- Use read-only for calculated fields
- Document security classifications
- Audit regularly
❌ DON'T:
- Hide required fields
- Restrict access unnecessarily
- Forget about API access
Security Checklist
Initial Setup
- Define user roles and hierarchies
- Create profiles for each role
- Set organization-wide defaults
- Configure field-level security
- Create sharing rules
Ongoing Maintenance
- Review user access quarterly
- Audit permission changes
- Remove inactive users promptly
- Update sharing rules as needed
- Monitor security health checks
Compliance
- Document security model
- Maintain access request process
- Log security changes
- Conduct security reviews
- Train users on security policies
Real-World Example
Complete security setup for a sales team:
// 1. Organization-Wide Defaults
OrganizationDefaults = {
account: { internalAccess: 'private' },
opportunity: { internalAccess: 'private' },
contact: { internalAccess: 'controlled_by_parent' },
};
// 2. Role Hierarchy
RoleHierarchy = {
roles: [
{ name: 'sales_vp', parentRole: null },
{ name: 'sales_manager', parentRole: 'sales_vp' },
{ name: 'sales_rep', parentRole: 'sales_manager' },
],
};
// 3. Profiles
SalesRepProfile = {
objectPermissions: {
account: { create: true, read: true, update: true, delete: false },
opportunity: { create: true, read: true, update: true, delete: false },
},
fieldPermissions: {
account: {
annual_revenue: { read: true, update: false }, // Read-only
},
},
};
// 4. Sharing Rules
AccountSharingRule = {
// Share high-value accounts with all sales reps
criteria: { annual_revenue: { $gte: 1000000 } },
sharedWith: { type: 'role', roles: ['sales_rep'] },
accessLevel: 'read_only',
};
// 5. Permission Set
AdvancedReportingPermissionSet = {
// For sales analysts
systemPermissions: {
runReports: true,
exportReports: true,
viewAllData: true,
},
};Next: Automation →