Skip to main content

Permissions Architecture

Source of Truth: Role.permissions JSON

Permissions in this application flow from WoW guild ranks to CASL abilities:

flowchart LR
A[WoW API] --> B[character.service.ts]
B --> C["Role.permissions (JSON)"]
C --> D[ability.factory.ts]
D --> E[CASL rules]

How It Works

1. WoW API Sync Sets Permissions

When characters are synced from Battle.net (character.service.ts), guild roles are created/updated with permissions based on WoW rank:

// character.service.ts - createMembershipWithRankName()
if (wowRank === 0) {
permissions = {
canManageGuild: true,
canManageMembers: true,
canManageEvents: true,
};
} else if (wowRank === 1) {
permissions = {
canManageGuild: true,
canManageMembers: true,
canManageEvents: true,
};
} else if (wowRank === 2) {
permissions = { canManageMembers: true, canManageEvents: true };
} else {
permissions = {};
}

These permissions are stored in Role.permissions (JSONB field in database).

2. CASL Reads from Role.permissions

The AbilityFactory reads Role.permissions and converts them to CASL abilities:

// ability.factory.ts - createForGuildMember()
const permissions = membership.role.permissions || {};

if (permissions.canManageGuild) {
can([Action.Update], 'Guild', { id: membership.guildId });
can([Action.Create, Action.Update, Action.Delete], 'Role', {
guildId: membership.guildId,
});
}

if (permissions.canManageMembers) {
can([Action.Update, Action.Invite, Action.Kick], 'Membership', {
guildId: membership.guildId,
});
}

if (permissions.canManageEvents) {
can([Action.Create, Action.Update, Action.Delete], 'Event', {
guildId: membership.guildId,
});
can(Action.Read, 'AttendanceStats');
}

if (permissions.canViewAttendance) {
can(Action.Read, 'AttendanceStats');
}

3. Frontend Uses CASL Abilities

The frontend receives serialized ability rules and uses them to show/hide UI:

<Can I={Action.Update} a="Guild">
<GuildSettings />
</Can>

Permission Levels by WoW Rank

Rank 0: Guild Master

  • canManageGuild: true → Can update guild settings, manage roles
  • canManageMembers: true → Can invite/kick members
  • canManageEvents: true → Can create/update/delete events (implies canViewAttendance)
  • canViewAttendance: true → Can view attendance statistics
  • Special: Gets Action.Manage on 'all' (full control)

Rank 1: Top Officer

  • canManageGuild: true → Can update guild settings, manage roles
  • canManageMembers: true → Can invite/kick members
  • canManageEvents: true → Can create/update/delete events (implies canViewAttendance)
  • canViewAttendance: true → Can view attendance statistics

Rank 2: Officer

  • canManageGuild: falseCannot update guild settings
  • canManageMembers: true → Can invite/kick members
  • canManageEvents: true → Can create/update/delete events (implies canViewAttendance)
  • canViewAttendance: true → Can view attendance statistics

Rank 3+: Regular Members

  • canManageGuild: false
  • canManageMembers: false
  • canManageEvents: false
  • canViewAttendance: false (can be granted independently)
  • Read-only access (subject to guild privacy settings)

Sync Source Restrictions

CASL permissions determine what a user is allowed to do, but the guild's syncSource determines which management features exist at all. For Blizzard-synced guilds:

  • Member role changes and removal are blocked (managed in-game)
  • Custom role creation is blocked (roles come from WoW ranks)
  • The UI hides these controls; the backend rejects attempts via API

These restrictions are enforced separately from CASL — a user may have canManageMembers: true but still cannot change roles in a Blizzard guild because the operation itself is not permitted for that guild type.

Why This Architecture?

Benefits

  1. Single Source of Truth: Role.permissions is authoritative
  2. WoW-Aligned: Permissions match WoW guild rank structure
  3. Extensible: Can add custom permissions in the future
  4. Auditable: Permissions are stored in DB, changes are tracked
  5. Flexible: Guild leaders could theoretically customize permissions

Separation of Concerns

  • character.service.ts: Syncs from WoW, sets permissions
  • ability.factory.ts: Reads permissions, creates CASL abilities
  • wowRank: Used for display/sorting, not for authorization logic

Guild Privacy Settings

Guild-specific settings (like rosterPrivacy) are applied on top of role permissions:

const rosterPrivacy = guildSettings.rosterPrivacy || 'members';

if (rosterPrivacy === 'officers' && wowRank > 2) {
// Block regular members from seeing roster
cannot(Action.Read, 'Membership');
}

This allows guilds to be more restrictive than the default role permissions.

Future: Custom Permissions

In the future, guild leaders could customize role permissions beyond WoW sync:

// Hypothetical: Guild leader overrides permissions for a specific role
await prisma.role.update({
where: { id: roleId },
data: {
permissions: {
canManageGuild: false,
canManageMembers: true,
canManageEvents: true,
canManageLoot: true, // Custom permission
},
},
});

CASL would automatically respect these custom permissions.

Implementation Checklist

When adding a new permission:

  1. ✅ Add to Role.permissions JSON structure (character.service.ts)
  2. ✅ Add corresponding CASL rule in ability.factory.ts
  3. ✅ Use @Can component or ability.can() in frontend
  4. ✅ Add guard decorator to backend endpoints

Example:

// 1. character.service.ts
if (wowRank === 0) {
permissions = { ..., canManageLoot: true };
}

// 2. ability.factory.ts
if (permissions.canManageLoot) {
can([Action.Create, Action.Update], 'LootDistribution');
}

// 3. Frontend
<Can I={Action.Update} a="LootDistribution">
<LootManager />
</Can>

// 4. Backend
@Put('loot/:id')
@RequireAbilities({ action: Action.Update, subject: 'LootDistribution' })
updateLoot() { }

Troubleshooting

Problem: User has rank 1 but can't access guild settings

Check:

  1. Does their Role.permissions include canManageGuild: true?

    SELECT r.name, r."wowRank", r.permissions
    FROM roles r
    WHERE r.id = 'role-id';
  2. Are they the highest-ranked character for that user in the guild?

  3. Was the character synced recently? (Old syncs might have outdated permissions)

Solution: Re-sync the character to update role permissions from WoW.