Implement enterprise role-based access control for Gamma integrations. Use when configuring team permissions, multi-tenant access, or enterprise authorization patterns. Trigger with phrases like "gamma RBAC", "gamma permissions", "gamma access control", "gamma enterprise", "gamma roles".
80
77%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./plugins/saas-packs/gamma-pack/skills/gamma-enterprise-rbac/SKILL.mdImplement role-based access control for Gamma API integrations. Gamma's API uses a single API key per workspace -- granular permissions must be implemented in your application layer. The Teams and Business plans support workspace-level collaboration with shared themes and folders.
gamma-install-auth setupGamma Workspace (1 API key)
├── Themes (shared across workspace)
├── Folders (shared across workspace)
└── Generations (tied to API key, not individual users)
Your Application Layer (you implement this):
├── Organization
│ ├── Admin (manage API key, configure themes)
│ ├── Editor (generate presentations, use templates)
│ ├── Viewer (view generated presentations, download exports)
│ └── Guest (no generation access)Key point: Gamma's API does not have per-user authentication. All API calls use the workspace API key. You must enforce per-user permissions in your application.
// src/auth/gamma-roles.ts
type GammaRole = "guest" | "viewer" | "editor" | "admin";
const PERMISSIONS: Record<GammaRole, string[]> = {
guest: [],
viewer: ["generation:view", "export:download"],
editor: ["generation:view", "generation:create", "export:download", "template:use"],
admin: [
"generation:view", "generation:create", "export:download",
"template:use", "template:manage", "theme:manage",
"settings:manage", "member:manage",
],
};
function hasPermission(role: GammaRole, permission: string): boolean {
return PERMISSIONS[role]?.includes(permission) ?? false;
}// src/middleware/gamma-auth.ts
import { Request, Response, NextFunction } from "express";
function requireGammaPermission(permission: string) {
return (req: Request, res: Response, next: NextFunction) => {
const user = req.user; // Set by your auth middleware
if (!user) return res.status(401).json({ error: "Unauthorized" });
if (!hasPermission(user.gammaRole, permission)) {
return res.status(403).json({
error: "Forbidden",
required: permission,
userRole: user.gammaRole,
});
}
next();
};
}
// Usage
app.post("/api/presentations",
requireGammaPermission("generation:create"),
async (req, res) => {
const gamma = createGammaClient({ apiKey: process.env.GAMMA_API_KEY! });
const { generationId } = await gamma.generate(req.body);
// Track ownership in your database
await db.generations.create({
data: { generationId, userId: req.user.id, teamId: req.user.teamId },
});
res.json({ generationId });
}
);
app.get("/api/presentations/:id",
requireGammaPermission("generation:view"),
async (req, res) => {
// Only return if user owns it or is in the same team
const gen = await db.generations.findFirst({
where: { generationId: req.params.id, teamId: req.user.teamId },
});
if (!gen) return res.status(404).json({ error: "Not found" });
res.json(gen);
}
);// src/tenant/gamma-tenant.ts
// Each tenant can have their own Gamma workspace (API key)
// or share a workspace with resource-level isolation
interface Tenant {
id: string;
name: string;
gammaApiKey: string; // Encrypted in database
}
class TenantGammaService {
private clients = new Map<string, ReturnType<typeof createGammaClient>>();
getClient(tenant: Tenant) {
if (!this.clients.has(tenant.id)) {
this.clients.set(
tenant.id,
createGammaClient({ apiKey: tenant.gammaApiKey })
);
}
return this.clients.get(tenant.id)!;
}
async generate(tenant: Tenant, userId: string, content: string, options: any = {}) {
const gamma = this.getClient(tenant);
const { generationId } = await gamma.generate({
content,
...options,
});
// Track with tenant isolation
await db.generations.create({
data: { generationId, tenantId: tenant.id, userId },
});
return { generationId };
}
}// src/quota/gamma-quotas.ts
interface Quota {
maxGenerationsPerDay: number;
maxCreditsPerMonth: number;
}
const ROLE_QUOTAS: Record<GammaRole, Quota> = {
guest: { maxGenerationsPerDay: 0, maxCreditsPerMonth: 0 },
viewer: { maxGenerationsPerDay: 0, maxCreditsPerMonth: 0 },
editor: { maxGenerationsPerDay: 10, maxCreditsPerMonth: 500 },
admin: { maxGenerationsPerDay: 50, maxCreditsPerMonth: 5000 },
};
async function checkQuota(userId: string, role: GammaRole): Promise<boolean> {
const quota = ROLE_QUOTAS[role];
if (quota.maxGenerationsPerDay === 0) return false;
const todayCount = await db.generations.count({
where: {
userId,
createdAt: { gte: new Date(new Date().toDateString()) },
},
});
return todayCount < quota.maxGenerationsPerDay;
}// src/audit/gamma-audit.ts
async function auditGammaAction(entry: {
userId: string;
teamId: string;
action: string;
resourceId?: string;
metadata?: Record<string, any>;
}) {
await db.auditLog.create({
data: {
...entry,
timestamp: new Date(),
service: "gamma",
},
});
}
// Usage
await auditGammaAction({
userId: req.user.id,
teamId: req.user.teamId,
action: "generation.create",
resourceId: generationId,
metadata: { outputFormat: "presentation", credits: result.creditsUsed },
});| Permission | Guest | Viewer | Editor | Admin |
|---|---|---|---|---|
| View presentations | No | Yes | Yes | Yes |
| Download exports | No | Yes | Yes | Yes |
| Create generations | No | No | Yes | Yes |
| Use templates | No | No | Yes | Yes |
| Manage themes/folders | No | No | No | Yes |
| Manage team members | No | No | No | Yes |
| Configure API key | No | No | No | Yes |
| Issue | Cause | Solution |
|---|---|---|
| 403 Forbidden | Insufficient role | Check user's gammaRole assignment |
| Cross-tenant access | Wrong API key | Verify tenant isolation in getClient() |
| Quota exceeded | Too many generations | Show remaining quota, wait for reset |
| Privilege escalation | Missing role check | Verify middleware on all routes |
Proceed to gamma-migration-deep-dive for platform migration.
3e83543
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.