| name | documenso-enterprise-rbac |
| description | Configure Documenso enterprise role-based access control and team management.
Use when implementing team permissions, configuring organizational roles,
or setting up enterprise access controls.
Trigger with phrases like "documenso RBAC", "documenso teams",
"documenso permissions", "documenso enterprise", "documenso roles".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Documenso Enterprise RBAC
Overview
Configure enterprise-grade role-based access control for Documenso integrations with team management and permission hierarchies.
Prerequisites
- Documenso Teams or Enterprise plan
- Understanding of RBAC concepts
- Identity provider (optional, for SSO)
Documenso Team Roles
| Role | Documents | Templates | Team Settings | API Access |
|---|
| Owner | Full | Full | Full | Full |
| Admin | Full | Full | Manage members | Full |
| Member | Create/Edit own | Use | None | Limited |
| Viewer | View only | View | None | Read-only |
Role Implementation
Step 1: Define Application Roles
export enum SigningRole {
Admin = "admin",
Manager = "manager",
User = "user",
Viewer = "viewer",
ApiService = "api_service",
}
export interface SigningPermissions {
documents: {
create: boolean;
read: boolean;
update: boolean;
delete: boolean;
send: boolean;
};
templates: {
create: boolean;
read: boolean;
update: boolean;
delete: boolean;
use: boolean;
};
team: {
manageMembers: boolean;
manageSettings: boolean;
viewAuditLog: boolean;
};
api: {
useApi: boolean;
manageWebhooks: boolean;
};
}
export const : <, > = {
[.]: {
: { : , : , : , : , : },
: { : , : , : , : , : },
: { : , : , : },
: { : , : },
},
[.]: {
: { : , : , : , : , : },
: { : , : , : , : , : },
: { : , : , : },
: { : , : },
},
[.]: {
: { : , : , : , : , : },
: { : , : , : , : , : },
: { : , : , : },
: { : , : },
},
[.]: {
: { : , : , : , : , : },
: { : , : , : , : , : },
: { : , : , : },
: { : , : },
},
[.]: {
: { : , : , : , : , : },
: { : , : , : , : , : },
: { : , : , : },
: { : , : },
},
};
Step 2: Permission Checking
import { SigningRole, ROLE_PERMISSIONS, SigningPermissions } from "./roles";
type PermissionPath =
| `documents.${keyof SigningPermissions["documents"]}`
| `templates.${keyof SigningPermissions["templates"]}`
| `team.${keyof SigningPermissions["team"]}`
| `api.${keyof SigningPermissions["api"]}`;
export function hasPermission(
role: SigningRole,
permission: PermissionPath
): boolean {
const [category, action] = permission.split(".") as [
keyof SigningPermissions,
string
];
const permissions = ROLE_PERMISSIONS[role];
return (permissions[category] as any)[action] ?? false;
}
export function checkPermission(
role: SigningRole,
permission: PermissionPath
): {
(!(role, permission)) {
(
);
}
}
{
() {
(message);
. = ;
}
}
Step 3: Express Middleware
import { Request, Response, NextFunction } from "express";
import { SigningRole, hasPermission } from "../auth";
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
role: SigningRole;
teamId?: string;
};
}
}
}
export function requireRole(requiredRole: SigningRole) {
return (req: Request, res: Response, next: NextFunction) => {
const user = req.user;
if (!user) {
return res.status(401).json({ error: "Authentication required" });
}
const roleHierarchy = [
.,
.,
.,
.,
];
userRoleIndex = roleHierarchy.(user.);
requiredRoleIndex = roleHierarchy.(requiredRole);
(userRoleIndex < requiredRoleIndex) {
res.().({
: ,
: ,
});
}
();
};
}
() {
{
user = req.;
(!user) {
res.().({ : });
}
(!(user., permission )) {
res.().({
: ,
: ,
});
}
();
};
}
Step 4: Document Ownership
interface DocumentOwnership {
documentId: string;
ownerId: string;
teamId?: string;
sharedWith: string[];
}
class DocumentAccessService {
private ownership = new Map<string, DocumentOwnership>();
async canAccess(
userId: string,
userRole: SigningRole,
documentId: string
): Promise<boolean> {
if (userRole === SigningRole.Admin) {
return true;
}
const ownership = this.ownership.get(documentId);
if (!ownership) {
return false;
}
if (ownership.ownerId === userId) {
return true;
}
if (ownership..(userId)) {
;
}
(userRole === . && ownership.) {
userTeam = .(userId);
userTeam === ownership.;
}
;
}
(
: ,
: ,
:
): <> {
(userRole === .) {
;
}
.(userId, userRole, documentId);
}
(
: ,
: ,
?:
): <> {
..(documentId, {
documentId,
ownerId,
teamId,
: [],
});
}
(
: ,
:
): <> {
ownership = ..(documentId);
(ownership) {
ownership..(shareWithUserId);
}
}
(: ): < | > {
;
}
}
documentAccess = ();
Step 5: API Route Protection
import express from "express";
import { requirePermission, requireRole } from "../middleware/auth";
import { SigningRole } from "../auth";
import { documentAccess } from "../services/document-access";
const router = express.Router();
router.post(
"/documents",
requirePermission("documents.create"),
async (req, res) => {
const { title, templateId } = req.body;
const userId = req.user!.id;
const doc = await signingService.createDocument(title, templateId);
await documentAccess.registerDocument(
doc.documentId,
userId,
req.user!.teamId
);
res.json(doc);
}
);
router.delete(
"/documents/:id",
requirePermission("documents.delete"),
async (req, res) => {
const documentId = req.params.;
{ : userId, role } = req.!;
canModify = documentAccess.(userId, role, documentId);
(!canModify) {
res.().({ : });
}
signingService.(documentId);
res.({ : });
}
);
router.(
,
(),
(req, res) => {
{ email, role } = req.;
res.({ : });
}
);
router.(
,
(),
(req, res) => {
auditLog = (req.!.!);
res.(auditLog);
}
);
router;
Step 6: Audit Logging
interface AuditEntry {
timestamp: Date;
userId: string;
userEmail: string;
userRole: SigningRole;
action: string;
resourceType: "document" | "template" | "team" | "settings";
resourceId: string;
details: Record<string, any>;
ipAddress?: string;
success: boolean;
}
class AuditLogger {
async log(entry: Omit<AuditEntry, "timestamp">): Promise<void> {
const fullEntry: AuditEntry = {
...entry,
timestamp: new Date(),
};
await db.auditLog.create({ data: fullEntry });
console.log(
+
);
(.(entry)) {
.(fullEntry);
}
}
(: <, >): {
(
!entry. ||
entry..() ||
entry..() ||
entry. ===
);
}
(: ): <> {
}
}
auditLogger = ();
() {
(: , : , : ) => {
startTime = .();
originalEnd = res.;
res. = () {
auditLogger.({
: req.?. ?? ,
: req.?. ?? ,
: req.?. ?? .,
action,
: resourceType ,
: req.. ?? ,
: {
: req.,
: req.,
: .() - startTime,
: res.,
},
: req.,
: res. < ,
});
originalEnd.(, chunk, encoding);
};
();
};
}
Multi-Tenant Architecture
interface TenantContext {
tenantId: string;
documensoApiKey: string;
features: {
templatesEnabled: boolean;
webhooksEnabled: boolean;
maxDocumentsPerMonth: number;
};
}
const tenantContexts = new Map<string, TenantContext>();
export function getTenantContext(tenantId: string): TenantContext {
const context = tenantContexts.get(tenantId);
if (!context) {
throw new Error(`Unknown tenant: ${tenantId}`);
}
return context;
}
export function getDocumensoClientForTenant(tenantId: string): Documenso {
const context = getTenantContext(tenantId);
return new Documenso({
apiKey: context.documensoApiKey,
});
}
Output
- Role-based permissions implemented
- Document ownership tracked
- Audit logging enabled
- Multi-tenant support ready
Error Handling
| RBAC Issue | Cause | Solution |
|---|
| 403 Forbidden | Insufficient role | Request role upgrade |
| Cannot delete | Not owner | Check ownership |
| Audit gap | Middleware missing | Add audit middleware |
| Tenant mismatch | Wrong context | Verify tenant ID |
Resources
Next Steps
For migration strategies, see documenso-migration-deep-dive.