Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
179
admin-ui/js/core/route-guards.js
Normal file
179
admin-ui/js/core/route-guards.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Route Guards - Phase 8 Enterprise Pattern
|
||||
*
|
||||
* Validates route access, enforces permissions, and handles
|
||||
* authentication/authorization before allowing navigation.
|
||||
*/
|
||||
|
||||
import store from '../stores/app-store.js';
|
||||
import auditLogger from './audit-logger.js';
|
||||
|
||||
class RouteGuard {
|
||||
constructor() {
|
||||
this.guards = new Map();
|
||||
this.setupDefaultGuards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a guard for a specific route
|
||||
*/
|
||||
register(route, guard) {
|
||||
this.guards.set(route, guard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can access route
|
||||
*/
|
||||
canActivate(route) {
|
||||
const state = store.get();
|
||||
|
||||
// Not authenticated
|
||||
if (!state.user) {
|
||||
auditLogger.logPermissionCheck('route_access', false, 'anonymous', `Unauthenticated access to ${route}`);
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'User must be logged in',
|
||||
redirect: '/#/login'
|
||||
};
|
||||
}
|
||||
|
||||
// Check route-specific guard
|
||||
const guard = this.guards.get(route);
|
||||
if (guard) {
|
||||
const result = guard(state);
|
||||
if (!result.allowed) {
|
||||
auditLogger.logPermissionCheck('route_access', false, state.user.id, `Access denied to ${route}: ${result.reason}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auditLogger.logPermissionCheck('route_access', true, state.user.id, `Access granted to ${route}`);
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user permission
|
||||
*/
|
||||
hasPermission(permission) {
|
||||
const state = store.get();
|
||||
if (!state.user) {
|
||||
auditLogger.logPermissionCheck('permission_check', false, 'anonymous', `Checking ${permission}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const allowed = store.hasPermission(permission);
|
||||
auditLogger.logPermissionCheck('permission_check', allowed, state.user.id, `Checking ${permission}`);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require permission before action
|
||||
*/
|
||||
requirePermission(permission) {
|
||||
if (!this.hasPermission(permission)) {
|
||||
throw new Error(`Permission denied: ${permission}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup default route guards
|
||||
*/
|
||||
setupDefaultGuards() {
|
||||
// Settings page - requires TEAM_LEAD or SUPER_ADMIN
|
||||
this.register('settings', (state) => {
|
||||
const allowed = ['TEAM_LEAD', 'SUPER_ADMIN'].includes(state.role);
|
||||
return {
|
||||
allowed,
|
||||
reason: allowed ? '' : 'Settings access requires team lead or admin role'
|
||||
};
|
||||
});
|
||||
|
||||
// Admin page - requires SUPER_ADMIN
|
||||
this.register('admin', (state) => {
|
||||
const allowed = state.role === 'SUPER_ADMIN';
|
||||
return {
|
||||
allowed,
|
||||
reason: allowed ? '' : 'Admin access requires super admin role'
|
||||
};
|
||||
});
|
||||
|
||||
// Projects page - requires active team
|
||||
this.register('projects', (state) => {
|
||||
const allowed = !!state.team;
|
||||
return {
|
||||
allowed,
|
||||
reason: allowed ? '' : 'Must select a team first'
|
||||
};
|
||||
});
|
||||
|
||||
// Figma integration - requires DEVELOPER or above
|
||||
this.register('figma', (state) => {
|
||||
const allowed = ['DEVELOPER', 'TEAM_LEAD', 'SUPER_ADMIN'].includes(state.role);
|
||||
return {
|
||||
allowed,
|
||||
reason: allowed ? '' : 'Figma integration requires developer or higher role'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate action before execution
|
||||
*/
|
||||
validateAction(action, resource) {
|
||||
const state = store.get();
|
||||
|
||||
if (!state.user) {
|
||||
throw new Error('User must be authenticated');
|
||||
}
|
||||
|
||||
const requiredPermission = this.getRequiredPermission(action, resource);
|
||||
if (!this.hasPermission(requiredPermission)) {
|
||||
throw new Error(`Permission denied: ${action} on ${resource}`);
|
||||
}
|
||||
|
||||
auditLogger.logAction(`${action}_${resource}`, {
|
||||
user: state.user.id,
|
||||
role: state.role
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map action to required permission
|
||||
*/
|
||||
getRequiredPermission(action, resource) {
|
||||
const permissions = {
|
||||
'create_project': 'write',
|
||||
'delete_project': 'write',
|
||||
'sync_figma': 'write',
|
||||
'export_tokens': 'read',
|
||||
'modify_settings': 'write',
|
||||
'manage_team': 'manage_team',
|
||||
'view_audit': 'read',
|
||||
};
|
||||
|
||||
return permissions[`${action}_${resource}`] || 'read';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available routes
|
||||
*/
|
||||
getRoutes() {
|
||||
return Array.from(this.guards.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if route is protected
|
||||
*/
|
||||
isProtected(route) {
|
||||
return this.guards.has(route);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export singleton
|
||||
const routeGuard = new RouteGuard();
|
||||
|
||||
export { RouteGuard };
|
||||
export default routeGuard;
|
||||
Reference in New Issue
Block a user