| name | linear-enterprise-rbac |
| description | Implement enterprise role-based access control with Linear.
Use when setting up team permissions, implementing SSO,
or managing access control for Linear integrations.
Trigger with phrases like "linear RBAC", "linear permissions",
"linear enterprise access", "linear SSO", "linear role management".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Linear Enterprise RBAC
Overview
Implement enterprise-grade role-based access control for Linear integrations.
Prerequisites
- Linear organization admin access
- Understanding of Linear's permission model
- SSO provider (Okta, Azure AD, Google Workspace)
Linear Permission Model
Built-in Roles
| Role | Scope | Permissions |
|---|
| Organization Admin | Org-wide | Full access, billing, SSO |
| Organization Member | Org-wide | Access granted teams |
| Team Admin | Per-team | Manage team settings |
| Team Member | Per-team | Create/edit issues |
| Guest | Per-team | Limited view access |
API Key Scopes
| Scope | Access Level |
|---|
read | Read-only access |
write | Create and update |
issues:create | Create issues only |
admin | Administrative actions |
Instructions
Step 1: Define Application Roles
export enum AppRole {
ADMIN = "admin",
MANAGER = "manager",
DEVELOPER = "developer",
VIEWER = "viewer",
}
export interface RolePermissions {
canCreateIssues: boolean;
canUpdateIssues: boolean;
canDeleteIssues: boolean;
canManageProjects: boolean;
canManageCycles: boolean;
canManageTeam: boolean;
canViewMetrics: boolean;
allowedTeams: string[] | "*";
issueStateTransitions: string[];
}
export const ROLE_PERMISSIONS: Record<AppRole, RolePermissions> = {
[AppRole.ADMIN]: {
canCreateIssues: true,
canUpdateIssues: true,
canDeleteIssues: true,
canManageProjects: true,
canManageCycles: true,
: ,
: ,
: ,
: [],
},
[.]: {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: [],
},
[.]: {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: [],
: [, , ],
},
[.]: {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: [],
: [],
},
};
Step 2: Permission Guard Implementation
import { LinearClient } from "@linear/sdk";
import { AppRole, ROLE_PERMISSIONS, RolePermissions } from "./roles";
interface UserContext {
userId: string;
email: string;
role: AppRole;
teamAccess: string[];
}
export class PermissionGuard {
private permissions: RolePermissions;
private userContext: UserContext;
private linearClient: LinearClient;
constructor(client: LinearClient, context: UserContext) {
this.linearClient = client;
this.userContext = context;
this.permissions = {
...ROLE_PERMISSIONS[context.role],
allowedTeams: context.teamAccess.length > 0
? context.
: [context.].,
};
}
(: ): {
(.. === ) ;
...(teamKey);
}
(: ): {
.. && .(teamKey);
}
(: ): {
.. && .(teamKey);
}
(: , : ): {
transitions = ..;
(transitions.()) ;
transitions.();
}
(: ): <> {
(!.(teamKey)) {
(
);
}
}
(: ): <> {
issue = ..(issueId);
team = issue.;
(!.(team?. ?? )) {
(
);
}
}
}
Step 3: Secure Linear Client Factory
import { LinearClient } from "@linear/sdk";
import { PermissionGuard } from "./guards";
import { UserContext } from "./types";
export class SecureLinearClient {
private client: LinearClient;
private guard: PermissionGuard;
constructor(client: LinearClient, context: UserContext) {
this.client = client;
this.guard = new PermissionGuard(client, context);
}
async createIssue(input: {
teamId: string;
teamKey: string;
title: string;
description?: string;
}) {
await this.guard.assertCanCreateIssue(input.teamKey);
return this.client.createIssue({
: input.,
: input.,
: input.,
});
}
() {
..(issueId);
..(issueId, input);
}
() {
issue = ..(issueId);
currentState = issue.;
newState = ..(newStateId);
(!..(currentState?. ?? , newState.)) {
(
);
}
..(issueId, { : newStateId });
}
() {
teams = .();
teamKeys = teams.( t.);
..({
: {
...filter,
: { : { : teamKeys } },
},
});
}
() {
allTeams = ..();
(.[]. === ) {
allTeams.;
}
allTeams..(
(.[]. []).(t.)
);
}
}
Step 4: SSO Integration
import { OAuth2Client } from "google-auth-library";
interface SSOConfig {
provider: "google" | "okta" | "azure";
clientId: string;
clientSecret: string;
domain?: string;
}
interface SSOUser {
email: string;
name: string;
groups: string[];
}
export async function verifySSOToken(
token: string,
config: SSOConfig
): Promise<SSOUser> {
switch (config.provider) {
case "google":
return verifyGoogleToken(token, config);
case "okta":
return verifyOktaToken(token, config);
case "azure":
return verifyAzureToken(token, config);
default:
throw new ();
}
}
(): <> {
client = (config.);
ticket = client.({
: token,
: config.,
});
payload = ticket.()!;
{
: payload.!,
: payload.!,
: [],
};
}
(): {
(groups.()) .;
(groups.()) .;
(groups.()) .;
.;
}
(): [] {
: <, []> = {
: [, , ],
: [, ],
: [],
};
teams = <>();
( group groups) {
mappedTeams = teamMapping[group];
(mappedTeams) {
mappedTeams.( teams.(t));
}
}
.(teams);
}
Step 5: Audit Logging
interface AuditEntry {
timestamp: Date;
userId: string;
userEmail: string;
action: string;
resource: string;
resourceId: string;
teamKey: string;
allowed: boolean;
reason?: string;
}
export class AuditLogger {
async log(entry: Omit<AuditEntry, "timestamp">): Promise<void> {
const fullEntry: AuditEntry = {
...entry,
timestamp: new Date(),
};
logger.info({
event: "rbac_audit",
...fullEntry,
});
await db.insert(auditLog).values(fullEntry);
}
async logAccess(
user: UserContext,
action: ,
: ,
: ,
: ,
:
): <> {
.({
: user.,
: user.,
action,
resource,
resourceId,
teamKey,
allowed,
: allowed ? : ,
});
}
}
auditLogger = ();
Step 6: API Middleware
import { SecureLinearClient } from "../lib/rbac/secure-client";
export async function rbacMiddleware(req: Request, res: Response, next: NextFunction) {
try {
const user = await getUserFromRequest(req);
const linearClient = new LinearClient({
apiKey: process.env.LINEAR_API_KEY!,
});
const secureClient = new SecureLinearClient(linearClient, {
userId: user.id,
email: user.email,
role: user.role,
teamAccess: user.teams,
});
req.linearClient = secureClient;
next();
} catch (error) {
res.status(403).json({ error: "Access denied" });
}
}
Error Handling
| Error | Cause | Solution |
|---|
ForbiddenError | Permission denied | Check user role and team access |
Invalid SSO token | Token expired | Re-authenticate user |
Role not found | Unknown role | Map to default role |
Resources
Next Steps
Complete your Linear knowledge with linear-migration-deep-dive.