๐Ÿ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

speak-enterprise-rbac

Configure Speak enterprise SSO, role-based access control, and organization management for language schools. Use when implementing SSO integration, configuring role-based permissions, or setting up organization-level controls for enterprise language learning. Trigger with phrases like "speak SSO", "speak RBAC", "speak enterprise", "speak roles", "speak permissions", "speak SAML". allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

speak-pack

Claude Code skill pack for Speak AI Language Learning Platform (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the speak-pack plugin:

/plugin install speak-pack@claude-code-plugins-plus

Click to copy

Instructions

# Speak Enterprise RBAC ## Overview Configure enterprise-grade access control for Speak language learning integrations in schools, businesses, and organizations. ## Prerequisites - Speak Enterprise tier subscription - Identity Provider (IdP) with SAML/OIDC support - Understanding of role-based access patterns - Audit logging infrastructure ## Role Definitions for Language Learning | Role | Permissions | Use Case | |------|-------------|----------| | Admin | Full access | Organization administrators | | Instructor | Create/manage courses, view learner progress | Teachers, tutors | | Manager | View reports, manage teams | Department heads | | Learner | Access assigned courses, track own progress | Students, employees | | Observer | Read-only access to progress | Parents, supervisors | | Service | API access only | Automated systems | ## Role Implementation ```typescript enum SpeakRole { Admin = 'admin', Instructor = 'instructor', Manager = 'manager', Learner = 'learner', Observer = 'observer', Service = 'service', } interface SpeakPermissions { // Lesson permissions createLessons: boolean; accessAllLanguages: boolean; assignCourses: boolean; // User permissions viewLearnerProgress: boolean; viewAllProgress: boolean; manageUsers: boolean; // Content permissions createContent: boolean; editContent: boolean; deleteContent: boolean; // Admin permissions manageBilling: boolean; manageSettings: boolean; viewAuditLogs: boolean; } const ROLE_PERMISSIONS: Record = { admin: { createLessons: true, accessAllLanguages: true, assignCourses: true, viewLearnerProgress: true, viewAllProgress: true, manageUsers: true, createContent: true, editContent: true, deleteContent: true, manageBilling: true, manageSettings: true, viewAuditLogs: true, }, instructor: { createLessons: true, accessAllLanguages: true, assignCourses: true, viewLearnerProgress: true, viewAllProgress: false, // Only their students manageUsers: false, createContent: true, editContent: true, deleteContent: false, manageBilling: false, manageSettings: false, viewAuditLogs: false, }, manager: { createLessons: false, accessAllLanguages: true, assignCourses: true, viewLearnerProgress: true, viewAllProgress: true, // Team members manageUsers: false, createContent: false, editContent: false, deleteContent: false, manageBilling: false, manageSettings: false, viewAuditLogs: false, }, learner: { createLessons: false, accessAllLanguages: false, // Based on plan assignCourses: false, viewLearnerProgress: false, viewAllProgress: false, manageUsers: false, createContent: false, editContent: false, deleteContent: false, manageBilling: false, manageSettings: false, viewAuditLogs: false, }, observer: { createLessons: false, accessAllLanguages: false, assignCourses: false, viewLearnerProgress: true, // Assigned learners only viewAllProgress: false, manageUsers: false, createContent: false, editContent: false, deleteContent: false, manageBilling: false, manageSettings: false, viewAuditLogs: false, }, service: { createLessons: false, accessAllLanguages: true, assignCourses: false, viewLearnerProgress: true, viewAllProgress: true, manageUsers: false, createContent: false, editContent: false, deleteContent: false, manageBilling: false, manageSettings: false, viewAuditLogs: true, }, }; function checkPermission( role: SpeakRole, permission: keyof SpeakPermissions ): boolean { return ROLE_PERMISSIONS[role][permission]; } ``` ## SSO Integration ### SAML Configuration ```typescript // Speak SAML setup for enterprise SSO const samlConfig = { entryPoint: 'https://idp.school.edu/saml/sso', issuer: 'https://speak.com/saml/metadata', cert: process.env.SAML_CERT, callbackUrl: 'https://app.yourschool.com/auth/speak/callback', identifierFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress', }; // Map IdP groups to Speak roles const groupRoleMapping: Record = { 'Faculty': SpeakRole.Instructor, 'Students': SpeakRole.Learner, 'Staff': SpeakRole.Learner, 'Department-Heads': SpeakRole.Manager, 'IT-Admins': SpeakRole.Admin, 'Parents': SpeakRole.Observer, }; // Extract role from SAML attributes function mapSamlToRole(samlAttributes: SamlAttributes): SpeakRole { const groups = samlAttributes.memberOf || []; // Check groups in priority order for (const [group, role] of Object.entries(groupRoleMapping)) { if (groups.includes(group)) { return role; } } // Default to learner return SpeakRole.Learner; } ``` ### OAuth2/OIDC Integration ```typescript import { OAuth2Client } from '@speak/sdk'; const oauthClient = new OAuth2Client({ clientId: process.env.SPEAK_OAUTH_CLIENT_ID!, clientSecret: process.env.SPEAK_OAUTH_CLIENT_SECRET!, redirectUri: 'https://app.yourschool.com/auth/speak/callback', scopes: [ 'lessons:read', 'lessons:write', 'progress:read', 'users:read', ], }); // Exchange code for tokens async function handleOAuthCallback(code: string): Promise { const tokens = await oauthClient.exchangeCode(code); // Get user info const userInfo = await oauthClient.getUserInfo(tokens.accessToken); // Map to internal user with role return { user: { id: userInfo.sub, email: userInfo.email, name: userInfo.name, role: mapOidcToRole(userInfo), }, tokens, }; } ``` ## Organization Management ```typescript interface SpeakOrganization { id: string; name: string; type: 'school' | 'business' | 'individual'; ssoEnabled: boolean; enforceSso: boolean; allowedDomains: string[]; defaultRole: SpeakRole; enabledLanguages: string[]; maxSeats: number; features: { customContent: boolean; progressReports: boolean; instructorDashboard: boolean; parentPortal: boolean; apiAccess: boolean; }; } async function createOrganization( config: Partial ): Promise { const org = await speakClient.organizations.create({ name: config.name!, type: config.type || 'business', settings: { sso: { enabled: config.ssoEnabled || false, enforced: config.enforceSso || false, domains: config.allowedDomains || [], }, defaults: { role: config.defaultRole || SpeakRole.Learner, languages: config.enabledLanguages || ['en'], }, features: config.features, }, }); await auditLog({ action: 'organization_created', organizationId: org.id, config, }); return org; } ``` ## Team and Class Management ```typescript interface Team { id: string; organizationId: string; name: string; type: 'class' | 'department' | 'cohort'; instructorIds: string[]; learnerIds: string[]; languages: string[]; curriculum?: CurriculumConfig; } class TeamManager { async createTeam(config: Partial): Promise { const team = await db.teams.insert({ ...config, id: crypto.randomUUID(), createdAt: new Date(), }); // Assign learners to team if (config.learnerIds) { await this.assignLearnersToTeam(team.id, config.learnerIds); } return team; } async assignLearnersToTeam(teamId: string, learnerIds: string[]): Promise { const team = await db.teams.findOne({ id: teamId }); for (const learnerId of learnerIds) { await db.teamMemberships.upsert({ teamId, userId: learnerId, role: 'learner', assignedAt: new Date(), languages: team.languages, }); // Create curriculum progress for learner if (team.curriculum) { await createCurriculumProgress(learnerId, team.curriculum); } } } async getTeamProgress(teamId: string): Promise { const team = await db.teams.findOne({ id: teamId }); const members = await db.teamMemberships.find({ teamId }); const progressData = await Promise.all( members.map(async (m) => ({ userId: m.userId, progress: await speakClient.progress.get(m.userId), })) ); return { team, totalLearners: members.length, averagePronunciation: calculateAverage(progressData, 'pronunciationScore'), lessonsCompleted: sum(progressData, 'lessonsCompleted'), averageStreak: calculateAverage(progressData, 'currentStreak'), languageBreakdown: aggregateByLanguage(progressData), }; } } ``` ## Access Control Middleware ```typescript function requireSpeakPermission( requiredPermission: keyof SpeakPermissions ) { return async (req: Request, res: Response, next: NextFunction) => { const user = req.user as { speakRole: SpeakRole; organizationId: string }; if (!checkPermission(user.speakRole, requiredPermission)) { await auditLog({ action: 'permission_denied', userId: user.id, permission: requiredPermission, resource: req.path, }); return res.status(403).json({ error: 'Forbidden', message: `Missing permission: ${requiredPermission}`, }); } next(); }; } // Middleware for resource ownership function requireResourceAccess(resourceType: 'team' | 'learner' | 'content') { return async (req: Request, res: Response, next: NextFunction) => { const user = req.user; const resourceId = req.params.id; const hasAccess = await checkResourceAccess(user, resourceType, resourceId); if (!hasAccess) { return res.status(403).json({ error: 'Forbidden', message: `No access to ${resourceType} ${resourceId}`, }); } next(); }; } // Usage app.get('/api/teams/:id/progress', requireSpeakPermission('viewLearnerProgress'), requireResourceAccess('team'), getTeamProgress ); app.delete('/api/content/:id', requireSpeakPermission('deleteContent'), requireResourceAccess('content'), deleteContent ); ``` ## Audit Trail ```typescript interface SpeakAuditEntry { timestamp: Date; userId: string; role: SpeakRole; organizationId: string; action: string; resource: string; resourceId?: string; success: boolean; ipAddress: string; userAgent: string; metadata?: Record; } async function logSpeakAccess(entry: Omit): Promise { const log: SpeakAuditEntry = { ...entry, timestamp: new Date() }; await auditDb.insert(log); // Alert on suspicious activity if (entry.action.includes('delete') && !entry.success) { await alertOnSuspiciousActivity(entry); } // Alert on unusual access patterns await detectAnomalousAccess(entry); } // Generate compliance reports async function generateAccessReport( organizationId: string, dateRange: DateRange ): Promise { const logs = await auditDb.find({ organizationId, timestamp: { $gte: dateRange.start, $lte: dateRange.end }, }); return { organizationId, period: dateRange, totalActions: logs.length, actionsByType: groupBy(logs, 'action'), actionsByRole: groupBy(logs, 'role'), failedAccessAttempts: logs.filter(l => !l.success), uniqueUsers: new Set(logs.map(l => l.userId)).size, }; } ``` ## Output - Role definitions for education/enterprise - SSO integration (SAML/OIDC) - Team and class management - Permission middleware - Audit trail enabled ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | SSO login fails | Wrong callback URL | Verify IdP config | | Permission denied | Missing role mapping | Update group mappings | | Token expired | Short TTL | Refresh token logic | | Team access denied | Not a member | Check team membership | ## Examples ### Quick Permission Check ```typescript if (!checkPermission(user.role, 'viewLearnerProgress')) { throw new ForbiddenError('Cannot view learner progress'); } ``` ### Instructor Dashboard Access ```typescript app.get('/instructor/dashboard', requireSpeakPermission('viewLearnerProgress'), async (req, res) => { const teams = await teamManager.getInstructorTeams(req.user.id); const progress = await Promise.all( teams.map(t => teamManager.getTeamProgress(t.id)) ); res.json({ teams, progress }); } ); ``` ## Resources - [Speak Enterprise Guide](https://developer.speak.com/docs/enterprise) - [SAML 2.0 Specification](https://wiki.oasis-open.org/security/FrontPage) - [OpenID Connect Spec](https://openid.net/specs/openid-connect-core-1_0.html) ## Next Steps For major migrations, see `speak-migration-deep-dive`.

Skill file: plugins/saas-packs/speak-pack/skills/speak-enterprise-rbac/SKILL.md