πŸ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

maintainx-enterprise-rbac

Configure enterprise role-based access control for MaintainX integrations. Use when implementing SSO, managing organization-level permissions, or setting up enterprise access controls with MaintainX. Trigger with phrases like "maintainx rbac", "maintainx sso", "maintainx enterprise", "maintainx permissions", "maintainx roles". allowed-tools: Read, Write, Edit, Bash(npm:*) version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

maintainx-pack

Claude Code skill pack for MaintainX CMMS (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the maintainx-pack plugin:

/plugin install maintainx-pack@claude-code-plugins-plus

Click to copy

Instructions

# MaintainX Enterprise RBAC ## Overview Configure enterprise-grade role-based access control for MaintainX integrations, including SSO integration, permission management, and audit logging. ## Prerequisites - MaintainX Enterprise plan - Identity Provider (IdP) with SAML/OIDC - Understanding of RBAC concepts ## MaintainX Role Hierarchy ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MaintainX Role Hierarchy β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ ORGANIZATION ADMIN β”‚ β”‚ β”‚ β”‚ Full access to all features, users, and settings β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β–Ό β–Ό β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ ADMIN β”‚ β”‚ SUPERVISOR β”‚ β”‚ REQUESTER β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Manage users β”‚ β”‚ Manage work β”‚ β”‚ Submit β”‚ β”‚ β”‚ β”‚ Manage assets β”‚ β”‚ Assign tasks β”‚ β”‚ requests only β”‚ β”‚ β”‚ β”‚ Full WO accessβ”‚ β”‚ View reports β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β–Ό β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ TECHNICIAN β”‚ β”‚ VIEWER β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Execute work β”‚ β”‚ Read-only β”‚ β”‚ β”‚ β”‚ Update status β”‚ β”‚ View reports β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Instructions ### Step 1: Role Definition ```typescript // src/rbac/roles.ts enum MaintainXRole { OrganizationAdmin = 'ORGANIZATION_ADMIN', Admin = 'ADMIN', Supervisor = 'SUPERVISOR', Technician = 'TECHNICIAN', Requester = 'REQUESTER', Viewer = 'VIEWER', } interface Permission { resource: string; actions: ('create' | 'read' | 'update' | 'delete' | 'assign')[]; } const rolePermissions: Record = { [MaintainXRole.OrganizationAdmin]: [ { resource: '*', actions: ['create', 'read', 'update', 'delete', 'assign'] }, ], [MaintainXRole.Admin]: [ { resource: 'workorders', actions: ['create', 'read', 'update', 'delete', 'assign'] }, { resource: 'assets', actions: ['create', 'read', 'update', 'delete'] }, { resource: 'locations', actions: ['create', 'read', 'update', 'delete'] }, { resource: 'users', actions: ['create', 'read', 'update'] }, ], [MaintainXRole.Supervisor]: [ { resource: 'workorders', actions: ['create', 'read', 'update', 'assign'] }, { resource: 'assets', actions: ['read'] }, { resource: 'locations', actions: ['read'] }, { resource: 'users', actions: ['read'] }, { resource: 'reports', actions: ['read'] }, ], [MaintainXRole.Technician]: [ { resource: 'workorders', actions: ['read', 'update'] }, { resource: 'assets', actions: ['read'] }, { resource: 'locations', actions: ['read'] }, ], [MaintainXRole.Requester]: [ { resource: 'workrequests', actions: ['create', 'read'] }, { resource: 'workorders', actions: ['read'] }, ], [MaintainXRole.Viewer]: [ { resource: 'workorders', actions: ['read'] }, { resource: 'assets', actions: ['read'] }, { resource: 'locations', actions: ['read'] }, { resource: 'reports', actions: ['read'] }, ], }; function hasPermission( role: MaintainXRole, resource: string, action: string ): boolean { const permissions = rolePermissions[role]; return permissions.some(p => { const resourceMatch = p.resource === '*' || p.resource === resource; const actionMatch = p.actions.includes(action as any); return resourceMatch && actionMatch; }); } export { MaintainXRole, hasPermission, rolePermissions }; ``` ### Step 2: SAML SSO Integration ```typescript // src/auth/saml-sso.ts import { Strategy as SamlStrategy } from 'passport-saml'; import passport from 'passport'; interface SamlConfig { entryPoint: string; issuer: string; cert: string; callbackUrl: string; } // SAML configuration const samlConfig: SamlConfig = { entryPoint: process.env.SAML_ENTRY_POINT!, // IdP SSO URL issuer: process.env.SAML_ISSUER!, // SP Entity ID cert: process.env.SAML_CERT!, // IdP Certificate callbackUrl: `${process.env.APP_URL}/auth/saml/callback`, }; // Map IdP groups to MaintainX roles const groupRoleMapping: Record = { 'MaintainX-Admins': MaintainXRole.Admin, 'MaintainX-Supervisors': MaintainXRole.Supervisor, 'MaintainX-Technicians': MaintainXRole.Technician, 'MaintainX-Requesters': MaintainXRole.Requester, 'MaintainX-Viewers': MaintainXRole.Viewer, }; // Configure Passport SAML strategy passport.use(new SamlStrategy( { ...samlConfig, passReqToCallback: true, }, async (req, profile, done) => { try { // Extract user info from SAML assertion const email = profile.email || profile.nameID; const groups = profile.groups || []; // Determine role from groups let role = MaintainXRole.Viewer; // Default role for (const [group, mappedRole] of Object.entries(groupRoleMapping)) { if (groups.includes(group)) { role = mappedRole; break; } } // Find or create user const user = await findOrCreateUser({ email, firstName: profile.firstName, lastName: profile.lastName, role, ssoId: profile.nameID, }); return done(null, user); } catch (error) { return done(error); } } )); // Routes app.get('/auth/saml', passport.authenticate('saml')); app.post('/auth/saml/callback', passport.authenticate('saml', { failureRedirect: '/login' }), (req, res) => { res.redirect('/'); } ); ``` ### Step 3: Permission Middleware ```typescript // src/middleware/authorization.ts import { Request, Response, NextFunction } from 'express'; interface AuthenticatedRequest extends Request { user?: { id: string; email: string; role: MaintainXRole; }; } // Check permission middleware function requirePermission(resource: string, action: string) { return (req: AuthenticatedRequest, res: Response, next: NextFunction) => { if (!req.user) { return res.status(401).json({ error: 'Unauthorized' }); } if (!hasPermission(req.user.role, resource, action)) { auditLogger.log({ type: 'ACCESS_DENIED', userId: req.user.id, resource, action, ip: req.ip, }); return res.status(403).json({ error: 'Forbidden', message: `You don't have permission to ${action} ${resource}`, }); } next(); }; } // Check role middleware function requireRole(...allowedRoles: MaintainXRole[]) { return (req: AuthenticatedRequest, res: Response, next: NextFunction) => { if (!req.user) { return res.status(401).json({ error: 'Unauthorized' }); } if (!allowedRoles.includes(req.user.role)) { return res.status(403).json({ error: 'Forbidden', message: `Required role: ${allowedRoles.join(' or ')}`, }); } next(); }; } // Usage app.get('/api/workorders', requirePermission('workorders', 'read'), getWorkOrders ); app.post('/api/workorders', requirePermission('workorders', 'create'), createWorkOrder ); app.delete('/api/workorders/:id', requirePermission('workorders', 'delete'), deleteWorkOrder ); // Admin-only endpoint app.get('/api/admin/users', requireRole(MaintainXRole.OrganizationAdmin, MaintainXRole.Admin), getUsers ); ``` ### Step 4: Location-Based Access Control ```typescript // src/rbac/location-access.ts interface LocationAccess { userId: string; locationIds: string[]; includeChildren: boolean; } class LocationAccessControl { private accessRules: Map = new Map(); setAccess(userId: string, locationIds: string[], includeChildren = true) { this.accessRules.set(userId, { userId, locationIds, includeChildren, }); } async canAccessWorkOrder(userId: string, workOrder: WorkOrder): Promise { const access = this.accessRules.get(userId); // No restrictions = full access if (!access || access.locationIds.length === 0) { return true; } // Check if work order's location is accessible if (!workOrder.locationId) { return true; // No location = accessible } if (access.locationIds.includes(workOrder.locationId)) { return true; } // Check child locations if enabled if (access.includeChildren) { const childLocations = await this.getChildLocations(access.locationIds); return childLocations.includes(workOrder.locationId); } return false; } private async getChildLocations(parentIds: string[]): Promise { // Recursively get all child location IDs const children: string[] = []; const locations = await maintainxClient.getLocations(); function findChildren(parentId: string) { locations.locations .filter(l => l.parentId === parentId) .forEach(l => { children.push(l.id); findChildren(l.id); }); } parentIds.forEach(findChildren); return children; } } // Apply location filter to queries async function filterByLocationAccess( userId: string, workOrders: WorkOrder[] ): Promise { const lac = new LocationAccessControl(); const filtered = []; for (const wo of workOrders) { if (await lac.canAccessWorkOrder(userId, wo)) { filtered.push(wo); } } return filtered; } ``` ### Step 5: Audit Logging ```typescript // src/rbac/audit.ts interface AuditEntry { timestamp: Date; type: 'ACCESS_GRANTED' | 'ACCESS_DENIED' | 'DATA_MODIFIED' | 'LOGIN' | 'LOGOUT'; userId: string; userEmail?: string; userRole?: MaintainXRole; resource?: string; resourceId?: string; action?: string; ip: string; userAgent?: string; details?: any; } class AuditLogger { private store: AuditStore; async log(entry: Omit): Promise { const fullEntry: AuditEntry = { ...entry, timestamp: new Date(), }; // Store in database await this.store.insert(fullEntry); // Log to console for immediate visibility console.log(`[AUDIT] ${entry.type}: ${entry.userId} - ${entry.resource}/${entry.action}`); // Alert on suspicious activity if (entry.type === 'ACCESS_DENIED') { await this.checkForSuspiciousActivity(entry.userId); } } private async checkForSuspiciousActivity(userId: string): Promise { // Check for repeated access denials const recentDenials = await this.store.count({ userId, type: 'ACCESS_DENIED', timestamp: { $gte: new Date(Date.now() - 5 * 60 * 1000) }, // Last 5 minutes }); if (recentDenials > 10) { await alertSecurityTeam({ type: 'SUSPICIOUS_ACCESS_PATTERN', userId, message: `User ${userId} has ${recentDenials} access denials in the last 5 minutes`, }); } } // Generate compliance report async generateComplianceReport(startDate: Date, endDate: Date): Promise { const entries = await this.store.find({ timestamp: { $gte: startDate, $lte: endDate }, }); return { period: { start: startDate, end: endDate }, totalEvents: entries.length, byType: this.groupBy(entries, 'type'), byUser: this.groupBy(entries, 'userId'), accessDenials: entries.filter(e => e.type === 'ACCESS_DENIED'), loginEvents: entries.filter(e => e.type === 'LOGIN'), }; } } const auditLogger = new AuditLogger(); export { auditLogger }; ``` ### Step 6: API Key Scoping ```typescript // src/rbac/api-keys.ts interface ScopedApiKey { id: string; name: string; keyHash: string; permissions: Permission[]; locationRestrictions?: string[]; createdBy: string; createdAt: Date; expiresAt?: Date; lastUsedAt?: Date; } class ApiKeyManager { async createScopedKey( name: string, permissions: Permission[], options?: { locationRestrictions?: string[]; expiresIn?: number; // days } ): Promise<{ key: string; id: string }> { const rawKey = generateSecureToken(32); const keyHash = hashApiKey(rawKey); const apiKey: ScopedApiKey = { id: generateId(), name, keyHash, permissions, locationRestrictions: options?.locationRestrictions, createdBy: getCurrentUserId(), createdAt: new Date(), expiresAt: options?.expiresIn ? new Date(Date.now() + options.expiresIn * 24 * 60 * 60 * 1000) : undefined, }; await this.store.insert(apiKey); return { key: rawKey, // Only returned once id: apiKey.id, }; } async validateKey(rawKey: string): Promise { const keyHash = hashApiKey(rawKey); const apiKey = await this.store.findOne({ keyHash }); if (!apiKey) return null; // Check expiration if (apiKey.expiresAt && apiKey.expiresAt < new Date()) { return null; } // Update last used await this.store.update( { id: apiKey.id }, { $set: { lastUsedAt: new Date() } } ); return apiKey; } } ``` ## Output - Role definitions implemented - SAML SSO integration - Permission middleware - Location-based access control - Audit logging - Scoped API keys ## Enterprise Security Checklist - [ ] SSO configured with IdP - [ ] Role mappings defined - [ ] Permission checks on all endpoints - [ ] Audit logging enabled - [ ] Location restrictions configured - [ ] API key rotation policy - [ ] Compliance reports automated ## Resources - [MaintainX Enterprise](https://www.getmaintainx.com/enterprise) - [SAML 2.0 Specification](https://wiki.oasis-open.org/security/FrontPage) - [OWASP Access Control](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/) ## Next Steps For complete platform migration, see `maintainx-migration-deep-dive`.

Skill file: plugins/saas-packs/maintainx-pack/skills/maintainx-enterprise-rbac/SKILL.md