agentsclimarketplace

Canva enterprise rbac

Skill ComeOnOliver/skillshub/skills/jeremylongshore/claude-code-plugins-plus-skills/canva-enterprise-rbac

🧠 The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.

Install
npx -y skills add ComeOnOliver/skillshub --skill canva-enterprise-rbac

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Configure Canva Enterprise organization access control and scope management. Use when implementing per-user scope control, managing Canva Enterprise features, or setting up organization-level Canva integration governance. Trigger with phrases like "canva enterprise", "canva RBAC", "canva roles", "canva permissions", "canva organization", "canva team".

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

6.7 KB, as published. Nobody here has run it

Canva Enterprise RBAC

Overview

Manage access control for Canva Connect API integrations at the organization level. The Canva API uses OAuth scopes (not roles) — your application layer implements RBAC on top of Canva's scope system.

Canva Enterprise Requirements

FeatureCanva Free/ProCanva Enterprise
Design Create/ReadYesYes
Export DesignsYesYes
Asset UploadYesYes
Brand TemplatesNoYes
Autofill APINoYes
Folders (advanced)LimitedYes
Comments APIYesYes

Key: Autofill and brand template APIs require the user to be a member of a Canva Enterprise organization.

Application-Level RBAC

// Your application controls what each user role can do with Canva

interface CanvaRole {
  name: string;
  scopes: string[];          // OAuth scopes to request
  allowedOperations: string[]; // Application-level operations
}

const CANVA_ROLES: Record<string, CanvaRole> = {
  viewer: {
    name: 'Viewer',
    scopes: ['design:meta:read'],
    allowedOperations: ['listDesigns', 'getDesign'],
  },
  creator: {
    name: 'Creator',
    scopes: ['design:meta:read', 'design:content:write', 'design:content:read', 'asset:write', 'asset:read'],
    allowedOperations: ['listDesigns', 'getDesign', 'createDesign', 'exportDesign', 'uploadAsset'],
  },
  admin: {
    name: 'Admin',
    scopes: [
      'design:meta:read', 'design:content:write', 'design:content:read',
      'asset:write', 'asset:read',
      'brandtemplate:meta:read', 'brandtemplate:content:read',
      'folder:read', 'folder:write',
      'comment:read', 'comment:write',
      'collaboration:event',
    ],
    allowedOperations: ['*'],
  },
};

// Request only the scopes needed for the user's role
function getScopesForRole(role: string): string[] {
  return CANVA_ROLES[role]?.scopes || CANVA_ROLES.viewer.scopes;
}

Permission Middleware

function requireCanvaOperation(operation: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const userRole = req.user?.canvaRole || 'viewer';
    const role = CANVA_ROLES[userRole];

    if (!role) {
      return res.status(403).json({ error: 'Unknown role' });
    }

    if (!role.allowedOperations.includes('*') && !role.allowedOperations.includes(operation)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: `Role '${userRole}' cannot perform '${operation}'`,
        requiredRole: Object.entries(CANVA_ROLES)
          .find(([, r]) => r.allowedOperations.includes(operation) || r.allowedOperations.includes('*'))
          ?.[0],
      });
    }

    next();
  };
}

// Usage
app.post('/api/designs',
  requireCanvaOperation('createDesign'),
  async (req, res) => {
    const result = await req.canva.createDesign(req.body);
    res.json(result);
  }
);

app.post('/api/autofill',
  requireCanvaOperation('autofillTemplate'),
  async (req, res) => {
    // Only admins can autofill — requires Enterprise + admin role
    const result = await req.canva.createAutofill(req.body);
    res.json(result);
  }
);

User Capabilities Check

// GET https://api.canva.com/rest/v1/users/me/capabilities
// Check what the authenticated user can do

async function checkUserCapabilities(token: string): Promise<{
  canAutofill: boolean;
  isEnterprise: boolean;
}> {
  try {
    const data = await canvaAPI('/users/me/capabilities', token);
    return {
      canAutofill: data.capabilities?.includes('autofill') || false,
      isEnterprise: data.capabilities?.includes('brand_template') || false,
    };
  } catch {
    return { canAutofill: false, isEnterprise: false };
  }
}

Scope-Based Access Control

// Track which scopes each user authorized
interface UserCanvaAuth {
  userId: string;
  grantedScopes: string[];   // Scopes the user consented to
  role: string;              // Application-assigned role
  connectedAt: Date;
}

// Check if a specific API call is authorized
function canPerformAction(
  userAuth: UserCanvaAuth,
  requiredScope: string
): boolean {
  // 1. Check application role allows the operation
  const role = CANVA_ROLES[userAuth.role];
  if (!role) return false;

  // 2. Check the required OAuth scope was granted by the user
  if (!userAuth.grantedScopes.includes(requiredScope)) {
    console.warn(`User ${userAuth.userId} missing scope: ${requiredScope}`);
    return false;
  }

  return true;
}

// If user needs additional scopes, redirect to re-authorize
function buildReAuthUrl(userId: string, additionalScopes: string[]): string {
  const existingScopes = userAuth.grantedScopes;
  const allScopes = [...new Set([...existingScopes, ...additionalScopes])];

  return getAuthorizationUrl({
    clientId: process.env.CANVA_CLIENT_ID!,
    redirectUri: process.env.CANVA_REDIRECT_URI!,
    scopes: allScopes,
    codeChallenge: generatePKCE().challenge,
    state: `reauth:${userId}`,
  });
}

Audit Logging

async function auditCanvaAction(entry: {
  userId: string;
  role: string;
  action: string;
  endpoint: string;
  success: boolean;
  designId?: string;
}): Promise<void> {
  await db.auditLog.insert({
    ...entry,
    service: 'canva-connect-api',
    timestamp: new Date(),
  });

  // Alert on permission escalation attempts
  if (!entry.success && entry.action === 'autofillTemplate') {
    console.warn(`Permission denied: user ${entry.userId} (role: ${entry.role}) attempted ${entry.action}`);
  }
}

Error Handling

IssueCauseSolution
403 on autofillNot Enterprise userCheck user capabilities first
Scope not grantedUser rejected consentShow scope explanation, re-auth
Role mismatchWrong role assignedUpdate user role in your DB
New scope neededFeature addedTrigger re-authorization flow

Resources

Next Steps

For major migrations, see canva-migration-deep-dive.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.