From algolia-pack
Configures Algolia enterprise RBAC: team-scoped API keys, Secured API Keys for multi-tenant access, dashboard team management, and audit logging.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin algolia-packThis skill is limited to using the following tools:
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.
Implements Algolia security: scoped API keys, secured keys for frontend, backend separation, environment setup, and rotation using TypeScript examples.
Provides patterns for Algolia search implementation using React InstantSearch hooks, indexing strategies, relevance tuning, and Next.js SSR integration.
Automates Algolia operations like indexing, searching, and record management via Composio toolkit and Rube MCP. Discovers current tool schemas before execution.
Share bugs, ideas, or general feedback.
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.
| ACL | Operations Allowed | Use For |
|---|---|---|
search | Search queries | Frontend, search-only clients |
browse | Browse/export all records | Data export, migration scripts |
addObject | Add or replace records | Indexing pipelines |
deleteObject | Delete records | Data cleanup, GDPR deletion |
editSettings | Modify index settings | Deployment scripts |
listIndexes | List all indices | Monitoring, health checks |
deleteIndex | Delete entire indices | Admin operations only |
analytics | Read analytics data | Dashboards, reporting |
recommendation | Algolia Recommend API | Product recommendations |
usage | Read usage data | Billing monitoring |
logs | Read API logs | Debugging, audit |
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;
}
// 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" }
// 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();
};
}
// 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('');
}
}
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.
referers restrictionfiltersmaxQueriesPerIPPerHour set on all non-admin keysindexes (not all)| Issue | Cause | Solution |
|---|---|---|
| 403 on search | Key missing search ACL | Check key permissions with getApiKey |
| 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 |
For major platform migrations, see algolia-migration-deep-dive.