Claude-code-plugins-plus-skills langchain-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/langchain-pack/skills/langchain-enterprise-rbac" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-langchain-enterprise-rbac && rm -rf "$T"
manifest: plugins/saas-packs/langchain-pack/skills/langchain-enterprise-rbac/SKILL.md
source content

LangChain Enterprise RBAC

Overview

Role-based access control for multi-tenant LangChain applications: permission models, model access gating, tenant-scoped vector stores, usage quotas, and FastAPI middleware integration.

Permission Model

// permissions.ts
enum Permission {
  CHAIN_INVOKE = "chain:invoke",
  CHAIN_STREAM = "chain:stream",
  MODEL_GPT4O = "model:gpt-4o",
  MODEL_GPT4O_MINI = "model:gpt-4o-mini",
  MODEL_CLAUDE = "model:claude-sonnet",
  TOOLS_EXECUTE = "tools:execute",
  ADMIN_CONFIG = "admin:config",
  ADMIN_USERS = "admin:users",
}

interface Role {
  name: string;
  permissions: Permission[];
}

const ROLES: Record<string, Role> = {
  viewer: {
    name: "viewer",
    permissions: [Permission.CHAIN_INVOKE, Permission.MODEL_GPT4O_MINI],
  },
  user: {
    name: "user",
    permissions: [
      Permission.CHAIN_INVOKE,
      Permission.CHAIN_STREAM,
      Permission.MODEL_GPT4O_MINI,
      Permission.TOOLS_EXECUTE,
    ],
  },
  power_user: {
    name: "power_user",
    permissions: [
      Permission.CHAIN_INVOKE,
      Permission.CHAIN_STREAM,
      Permission.MODEL_GPT4O_MINI,
      Permission.MODEL_GPT4O,
      Permission.MODEL_CLAUDE,
      Permission.TOOLS_EXECUTE,
    ],
  },
  admin: {
    name: "admin",
    permissions: Object.values(Permission),
  },
};

User and Tenant Models

interface Tenant {
  id: string;
  name: string;
  monthlyTokenLimit: number;
  tokensUsed: number;
  allowedModels: string[];
}

interface User {
  id: string;
  tenantId: string;
  role: string;
  email: string;
}

function hasPermission(user: User, permission: Permission): boolean {
  const role = ROLES[user.role];
  if (!role) return false;
  return role.permissions.includes(permission);
}

function canUseModel(user: User, tenant: Tenant, model: string): boolean {
  const modelPermission = `model:${model}` as Permission;
  return (
    hasPermission(user, modelPermission) &&
    tenant.allowedModels.includes(model)
  );
}

Middleware: Permission Enforcement

// Express middleware
import { Request, Response, NextFunction } from "express";

function requirePermission(permission: Permission) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = req.user as User;  // set by auth middleware
    if (!user) {
      return res.status(401).json({ error: "Authentication required" });
    }
    if (!hasPermission(user, permission)) {
      return res.status(403).json({
        error: `Missing permission: ${permission}`,
        role: user.role,
      });
    }
    next();
  };
}

// Usage
app.post("/api/chat",
  requirePermission(Permission.CHAIN_INVOKE),
  async (req, res) => {
    const result = await chain.invoke({ input: req.body.input });
    res.json({ result });
  }
);

app.post("/api/chat/stream",
  requirePermission(Permission.CHAIN_STREAM),
  async (req, res) => {
    // streaming endpoint...
  }
);

Model Access Control

import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";

class ModelGateway {
  private models: Record<string, any> = {};

  constructor() {
    this.models["gpt-4o-mini"] = new ChatOpenAI({ model: "gpt-4o-mini" });
    this.models["gpt-4o"] = new ChatOpenAI({ model: "gpt-4o" });
    this.models["claude-sonnet"] = new ChatAnthropic({ model: "claude-sonnet-4-20250514" });
  }

  getModel(modelName: string, user: User, tenant: Tenant) {
    if (!canUseModel(user, tenant, modelName)) {
      throw new Error(
        `User ${user.email} (role: ${user.role}) cannot access model: ${modelName}`
      );
    }

    if (tenant.tokensUsed >= tenant.monthlyTokenLimit) {
      throw new Error(
        `Tenant ${tenant.name} exceeded monthly token limit (${tenant.monthlyTokenLimit})`
      );
    }

    return this.models[modelName];
  }
}

Tenant-Scoped Vector Store

// Isolate each tenant's documents in the vector store
import { PineconeStore } from "@langchain/pinecone";

class TenantScopedRetriever {
  constructor(
    private vectorStore: PineconeStore,
    private tenantId: string,
  ) {}

  async retrieve(query: string, k = 4) {
    // Filter by tenant ID — prevents cross-tenant data leakage
    return this.vectorStore.similaritySearch(query, k, {
      tenantId: { $eq: this.tenantId },
    });
  }

  asRetriever(k = 4) {
    return this.vectorStore.asRetriever({
      k,
      filter: { tenantId: { $eq: this.tenantId } },
    });
  }
}

// When ingesting documents, always tag with tenant ID
async function ingestForTenant(tenantId: string, docs: any[]) {
  const tagged = docs.map((doc) => ({
    ...doc,
    metadata: { ...doc.metadata, tenantId },
  }));
  await vectorStore.addDocuments(tagged);
}

Usage Quota Tracking

class UsageQuotaManager {
  private usage = new Map<string, number>();

  async trackUsage(tenantId: string, tokens: number) {
    const current = this.usage.get(tenantId) ?? 0;
    this.usage.set(tenantId, current + tokens);
  }

  async checkQuota(tenant: Tenant): Promise<boolean> {
    const used = this.usage.get(tenant.id) ?? 0;
    return used < tenant.monthlyTokenLimit;
  }

  async getUsageReport(tenantId: string) {
    return {
      tenantId,
      tokensUsed: this.usage.get(tenantId) ?? 0,
      period: new Date().toISOString().slice(0, 7), // YYYY-MM
    };
  }
}

// Integrate with callback
class QuotaCallback extends BaseCallbackHandler {
  name = "QuotaCallback";

  constructor(
    private quotaManager: UsageQuotaManager,
    private tenantId: string,
  ) { super(); }

  async handleLLMEnd(output: any) {
    const tokens = output.llmOutput?.tokenUsage?.totalTokens ?? 0;
    await this.quotaManager.trackUsage(this.tenantId, tokens);
  }
}

Python FastAPI Equivalent

from fastapi import Depends, HTTPException

async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = decode_jwt(token)
    if not user:
        raise HTTPException(status_code=401)
    return user

def require_permission(permission: str):
    async def checker(user = Depends(get_current_user)):
        if permission not in ROLES[user.role]["permissions"]:
            raise HTTPException(status_code=403, detail=f"Missing: {permission}")
        return user
    return checker

@app.post("/api/chat")
async def chat(input: dict, user = Depends(require_permission("chain:invoke"))):
    return await chain.ainvoke({"input": input["text"]})

Error Handling

IssueCauseFix
401 UnauthorizedMissing or invalid tokenCheck auth middleware, token format
403 ForbiddenInsufficient permissionsUpgrade user role or add permission
Tenant data leakMissing filter on vector storeAlways filter by
tenantId
Quota exceededHigh usageIncrease tenant limit or optimize

Resources

Next Steps

Use

langchain-data-handling
for tenant-scoped RAG pipelines.