Claude-code-plugins-plus-skills algolia-enterprise-rbac
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/algolia-pack/skills/algolia-enterprise-rbac" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-algolia-enterprise-rbac && rm -rf "$T"
manifest:
plugins/saas-packs/algolia-pack/skills/algolia-enterprise-rbac/SKILL.mdsource content
Algolia Enterprise RBAC
Overview
Algolia's access control is built on API keys with ACL (Access Control Lists). Each key has specific permissions, index restrictions, and rate limits. For multi-tenant apps, Secured API Keys provide per-user filtering without creating individual keys. For team management, Algolia's dashboard supports team members with role-based access.
API Key ACL Permissions
| ACL | Operations Allowed | Use For |
|---|---|---|
| Search queries | Frontend, search-only clients |
| Browse/export all records | Data export, migration scripts |
| Add or replace records | Indexing pipelines |
| Delete records | Data cleanup, GDPR deletion |
| Modify index settings | Deployment scripts |
| List all indices | Monitoring, health checks |
| Delete entire indices | Admin operations only |
| Read analytics data | Dashboards, reporting |
| Algolia Recommend API | Product recommendations |
| Read usage data | Billing monitoring |
| Read API logs | Debugging, audit |
Instructions
Step 1: Define Application Roles
import { algoliasearch } from 'algoliasearch'; const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!); // Role definitions with minimal permissions const ROLES = { // Backend search service: search only, scoped to specific indices searchService: { acl: ['search'] as const, description: 'Search service — production read-only', indexes: ['products', 'articles'], maxQueriesPerIPPerHour: 100000, }, // Indexing pipeline: write records, no search or delete indexingPipeline: { acl: ['addObject', 'editSettings', 'listIndexes'] as const, description: 'Indexing pipeline — write-only, no delete', indexes: ['products', 'articles'], maxQueriesPerIPPerHour: 10000, }, // Analytics dashboard: read analytics, no data access analyticsDashboard: { acl: ['analytics', 'usage', 'listIndexes'] as const, description: 'Analytics reader — no record access', indexes: ['products', 'articles'], maxQueriesPerIPPerHour: 5000, }, // Data admin: full CRUD, restricted to non-production dataAdmin: { acl: ['search', 'browse', 'addObject', 'deleteObject', 'editSettings', 'listIndexes', 'deleteIndex'] as const, description: 'Data admin — full access, staging only', indexes: ['staging_*'], maxQueriesPerIPPerHour: 50000, }, }; async function createRoleKey(roleName: keyof typeof ROLES) { const role = ROLES[roleName]; const { key } = await client.addApiKey({ apiKey: { acl: [...role.acl], description: role.description, indexes: role.indexes, maxQueriesPerIPPerHour: role.maxQueriesPerIPPerHour, }, }); console.log(`Created ${roleName} key: ...${key.slice(-8)}`); return key; }
Step 2: Multi-Tenant RBAC with Secured API Keys
// Secured API Keys embed filters the client cannot bypass. // Generate on YOUR server, send to the frontend. interface UserContext { userId: string; tenantId: string; role: 'admin' | 'editor' | 'viewer'; } function generateUserSearchKey(user: UserContext): string { // Base filter: tenant isolation let filters = `tenant_id:${user.tenantId}`; // Role-based visibility switch (user.role) { case 'admin': // Admins see everything in their tenant break; case 'editor': // Editors see published + their own drafts filters += ` AND (status:published OR author_id:${user.userId})`; break; case 'viewer': // Viewers see published only filters += ' AND status:published'; break; } return client.generateSecuredApiKey({ parentApiKey: process.env.ALGOLIA_SEARCH_KEY!, restrictions: { filters, validUntil: Math.floor(Date.now() / 1000) + 3600, // 1 hour restrictIndices: ['products', 'articles'], }, }); } // API endpoint: generate key for authenticated user // GET /api/algolia/key // Response: { appId: "...", searchKey: "secured_key_here" }
Step 3: Permission Checking Middleware
// Validate that the calling service has required Algolia permissions async function validateKeyPermissions( apiKey: string, requiredAcl: string[] ): Promise<boolean> { try { const keyInfo = await client.getApiKey({ key: apiKey }); const hasAll = requiredAcl.every(perm => keyInfo.acl.includes(perm)); if (!hasAll) { const missing = requiredAcl.filter(p => !keyInfo.acl.includes(p)); console.warn(`Key missing permissions: ${missing.join(', ')}`); } return hasAll; } catch (e) { console.error('Failed to validate API key:', e); return false; } } // Express middleware function requireAlgoliaPermission(requiredAcl: string[]) { return async (req: any, res: any, next: any) => { const key = req.headers['x-algolia-api-key']; if (!key || !(await validateKeyPermissions(key, requiredAcl))) { return res.status(403).json({ error: 'Insufficient Algolia permissions' }); } next(); }; }
Step 4: API Key Audit and Rotation
// List all API keys and audit their permissions async function auditApiKeys() { const { keys } = await client.listApiKeys(); console.log(`Total API keys: ${keys.length}\n`); for (const key of keys) { const ageMs = Date.now() - new Date(key.createdAt * 1000).getTime(); const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24)); console.log(`Key: ...${key.value.slice(-8)}`); console.log(` Description: ${key.description || '(none)'}`); console.log(` ACL: ${key.acl.join(', ')}`); console.log(` Indices: ${key.indexes?.join(', ') || 'ALL'}`); console.log(` Rate limit: ${key.maxQueriesPerIPPerHour || 'unlimited'}/hr`); console.log(` Age: ${ageDays} days`); // Flag old keys if (ageDays > 90) { console.log(` WARNING: Key is ${ageDays} days old — consider rotation`); } // Flag overly permissive keys if (key.acl.includes('deleteIndex') && !key.description?.includes('admin')) { console.log(` WARNING: Has deleteIndex permission — verify this is intentional`); } console.log(''); } }
Step 5: Dashboard Team Management
Algolia Dashboard Team Roles (configured in dashboard.algolia.com > Team): | Dashboard Role | Can Do | Can't Do | |----------------|-------------------------------------------|-----------------------| | Owner | Everything + billing + team management | N/A | | Admin | All index operations + API key management | Billing | | Editor | Search, index data, edit settings | API key management | | Viewer | Search, view analytics | Modify anything | Configure at: dashboard.algolia.com > Settings > Team Enterprise plans support SSO (SAML 2.0) for team authentication.
Security Checklist
- Each microservice has its own scoped API key (not shared admin key)
- Frontend keys are search-only with
restrictionreferers - Multi-tenant apps use Secured API Keys with
filters -
set on all non-admin keysmaxQueriesPerIPPerHour - Keys restricted to specific
(not all)indexes - Key rotation scheduled (every 90 days)
- Dashboard team members have appropriate roles
- API key audit runs monthly
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| 403 on search | Key missing ACL | Check key permissions with |
| Secured key invalid | Parent key deleted/rotated | Regenerate secured keys from new parent |
| Filter bypass | Client-side filter manipulation | Secured API Keys enforce filters server-side |
| Audit shows unknown keys | Leaked or forgotten keys | Delete unrecognized keys, rotate known ones |
Resources
Next Steps
For major platform migrations, see
algolia-migration-deep-dive.