Skip to main content
Concordia implements a sophisticated role-based access control (RBAC) system combined with attribute-based access control (ABAC) to manage user permissions across the platform.

Overview

The permission system is built using Better Auth’s access control plugin and extends it with Concordia-specific roles and permissions. The system supports:
  • Multi-role assignment: Users can hold multiple roles simultaneously
  • Hierarchical permissions: Permissions are organized by domain (places, articles, forum, etc.)
  • Contextual access control: Some permissions require additional context (ownership, organization membership, etc.)
  • Privilege escalation prevention: Built-in safeguards against unauthorized role changes

User Roles

Concordia defines seven distinct roles, each with specific permissions and responsibilities.

Anonyme (Anonymous)

Description: Non-authenticated visitors Access:
  • Public consultation of places, articles, forum threads, classifieds, services, events, and trails
  • No ability to create content or interact
  • Read-only access to public information

Citoyen (Citizen)

Description: Default role for all authenticated users Capabilities:
  • Manage personal profile
  • Create and manage reviews, comments, forum posts
  • Send and receive private messages
  • Create and manage classifieds, events, groups, galleries
  • Access personal wallet and make transactions
  • Create organizations and projects
  • Enroll in education modules
  • Participate in volunteer projects and funding campaigns
Permissions (from permissions.ts:178-237):
citizen: new Set([
  "read_profile",
  "update_own_profile",
  "create_organization",
  "place.read",
  "article.read",
  "review.create",
  "review.read",
  "review.update_own",
  "review.delete_own",
  "forum.create_thread",
  "forum.create_post",
  "forum.update_own_post",
  "forum.delete_own_post",
  "classified.create",
  "classified.update_own",
  "classified.delete_own",
  "event.create",
  "event.update_own",
  "event.delete_own",
  "message.send",
  "message.read_own",
  "wallet.read_own",
  "wallet.credit",
  "wallet.transfer",
  // ... and more
])

Propriétaire (Owner)

Description: Citizens who manage one or more establishments or places Additional Capabilities:
  • Submit and manage place listings
  • Update place information and attributes
  • Manage service availability and bookings
  • Respond to reviews
  • Access booking revenue
  • Manage organization-level resources
Key Permissions (from permissions.ts:238-252):
owner: new Set([
  "place.create",
  "place.read",
  "place.update_own",
  "place.delete_own",
  "booking.manage_own_service",
  "manage_organization",
  "delete_organization",
  "invite_member",
  "remove_member",
  "update_resource",
  "read_resource",
])

Auteur (Author)

Description: Citizens who publish editorial content Capabilities:
  • Create and manage blog articles
  • Publish content via back-office
  • Link articles to places and categories
  • Manage article drafts and submissions
Permissions (from permissions.ts:253-258):
author: new Set([
  "article.create",
  "article.read",
  "article.update_own",
  "article.delete_own",
])

Médiateur (Mediator)

Description: Facilitators of conflict resolution between parties Capabilities:
  • Conduct mediation sessions
  • Sign mediation agreements
  • Manage assigned mediation cases
Permissions (from permissions.ts:259-262):
mediator: new Set([
  "mediation.conduct_session",
  "mediation.sign_agreement",
])

Éducateur (Educator)

Description: Content creators for educational modules Capabilities:
  • Create and update educational modules
  • Manage course content and lessons
  • Track student progress
Permissions (from permissions.ts:263-266):
educator: new Set([
  "education.create_module",
  "education.update_own_module",
])

Modérateur (Moderator)

Description: Content moderators with oversight of community contributions Capabilities:
  • Access moderation queue
  • Take moderation actions (approve, hide, delete)
  • Delete any user-generated content (reviews, comments, forum posts, classifieds)
  • Pin and lock forum threads
  • Manage community standards enforcement
Permissions (from permissions.ts:267-280):
moderator: new Set([
  "moderation.queue",
  "moderation.action",
  "review.delete_any",
  "comment.delete_any",
  "forum.delete_any_post",
  "forum.pin_thread",
  "forum.lock_thread",
  "classified.delete_any",
  "event.delete_any",
  "group.delete_any",
  "gallery.delete_any",
  "product.delete_any",
])

Administrateur (Administrator)

Description: Platform administrators with full system access Capabilities:
  • User management (create, delete, impersonate)
  • Role assignment and management
  • Organization administration
  • Taxonomy management (categories, attributes, tags)
  • Content approval and moderation
  • Access audit logs and analytics
  • Publish transparency reports
  • System configuration
Permissions (from permissions.ts:281-329):
admin: new Set([
  "create_user",
  "delete_user",
  "impersonate_user",
  "admin_access",
  "manage_organization",
  "change_role",
  "place.update_any",
  "place.delete_any",
  "place.approve",
  "place.reject",
  "article.update_any",
  "article.delete_any",
  "article.approve",
  "taxonomy.manage",
  "moderation.queue",
  "moderation.action",
  "transparency.publish_report",
  "admin.users",
  "admin.audit",
  "admin.config",
  // ... plus all moderator permissions
])

Permission Model

RBAC Matrix

Permissions are defined in a role-based matrix (appRbacMatrix) where each role has a set of allowed permissions. The system checks if any of the user’s roles grants the requested permission.

Permission Check Function

// From permissions.ts:336-341
export function hasPermission(
  userRoles: AppRole[],
  permission: AppPermission,
): boolean {
  return userRoles.some((role) => appRbacMatrix[role]?.has(permission));
}

ABAC (Attribute-Based Access Control)

Some permissions require contextual validation beyond role membership:
// From permissions.ts:347-473
export async function checkPermission(
  roleOrRoles: AppRole | AppRole[],
  permission: AppPermission,
  context?: ABACContext,
): Promise<boolean>
Context-aware rules include:
Permissions ending in _own require that the user owns the resource:
if (permission.endsWith('_own') || permission.includes('_own_')) {
  if (context?.resourceOwnerId && context?.userId) {
    return context.resourceOwnerId === context.userId;
  }
}
Admin access is restricted by time and IP address:
if (permission === 'admin_access') {
  const hour = context?.hour ?? new Date().getHours();
  const allowedHour = hour >= 8 && hour <= 18; // business hours
  if (!allowedHour) return false;
  
  const ip = context?.ip as string | undefined;
  if (ip) {
    const internal = ip.startsWith('10.') || 
                    ip.startsWith('192.168.') || 
                    ip.startsWith('172.');
    if (!internal) return false;
  }
}
Prevents users from assigning themselves admin role:
if (permission === 'change_role') {
  if (context?.targetRole === 'admin') {
    throw new Error('Privilege escalation attempt');
  }
}
Prevents users from reviewing or booking their own places:
if (permission === 'review.create' || permission === 'booking.create') {
  if (context?.targetOwnerId && context?.userId && 
      context.targetOwnerId === context.userId) {
    return false;
  }
}

Permission Categories

Permissions are organized by domain:
DomainExamples
Authcreate_user, delete_user, impersonate_user, admin_access
Placesplace.create, place.read, place.update_own, place.approve
Articlesarticle.create, article.read, article.update_any
Reviewsreview.create, review.update_own, review.delete_any
Forumforum.create_thread, forum.create_post, forum.pin_thread
Classifiedsclassified.create, classified.update_own, classified.approve
Eventsevent.create, event.update_own, event.delete_any
Messagingmessage.send, message.read_own
Economywallet.read_own, wallet.credit, wallet.transfer
Moderationmoderation.queue, moderation.action
Adminadmin.users, admin.audit, admin.config

Multi-Role System

Users can hold multiple roles simultaneously. For example:
  • A user can be both Citoyen (default) and Propriétaire (managing places)
  • An Auteur can also be a Modérateur
  • All roles accumulate permissions (union of all role permissions)
Role assignment (from concordia-specs.md:256-276):
CREATE TABLE user_role (
  id uuid PRIMARY KEY,
  user_id uuid REFERENCES user(id),
  role app_role NOT NULL,
  granted_by uuid REFERENCES user(id),
  granted_at timestamp DEFAULT now(),
  UNIQUE(user_id, role)
);

Best Practices

1

Always check permissions before actions

Use hasPermission() or checkPermission() before allowing users to perform actions.
2

Provide context for ownership checks

When checking _own permissions, always pass the resourceOwnerId and userId in the context.
3

Handle permission errors gracefully

Return appropriate HTTP status codes (401 for unauthenticated, 403 for unauthorized).
4

Audit role changes

All role assignments and removals should be logged in the audit log.

See Also