6. Auth & Permissions
Authentication Mechanisms
Sasha Studio uses four authentication mechanisms, applied by middleware in server/middleware/auth.js.
1. JWT Token Authentication
Middleware: authenticateToken()
- Generation:
generateToken(user)creates a JWT containing{ userId, username, isAdmin } - Expiry: None. Tokens do not expire.
- Secret:
JWT_SECRETenv var (falls back to hardcoded dev default) - Transport:
Authorization: Bearer <token>header - Storage: Client stores in localStorage
- Validation: Standard JWT verify, then user lookup by
userIdfrom token payload
2. API Key Authentication
Middleware: authenticateApiKey()
- Format:
sk_<random>prefix - Storage: bcrypt hash in
api_keys.key_hash - Transport:
X-API-Key: <key>header orAuthorization: Bearer sk_<key> - Validation: Iterate all active keys, bcrypt compare each (note: O(n) in number of keys)
- Usage tracking:
last_used_atupdated on each successful use - Signing: Each key has an HMAC
signing_secretfor callback payload verification
3. Localhost Bypass
Middleware: authenticateTokenOrLocalhost(), authenticateApiKeyOrLocalhost()
- Detection: Request IP checked against:
127.0.0.1,::1,::ffff:127.0.0.1,localhost - Extended detection: Also matches
172.*,10.*,192.168.*(Docker networking) - Purpose: Allows Claude CLI process (running locally in container) to call APIs without auth
- Usage: Cron execution, MCP CLI listing, internal operations
4. WebSocket Authentication
Middleware: authenticateWebSocket()
- JWT token passed as query parameter on WebSocket connection
- Validated same as HTTP JWT
- Connection rejected if invalid
Authorization Model
Role Model
Binary admin/non-admin. No roles, no RBAC, no permissions matrix.
| Check | Implementation |
|---|---|
| Is admin? | user.is_admin === 1 (or isAdmin from JWT) |
| Is authenticated? | Valid JWT in request |
| Is localhost? | IP address check |
Middleware Combinations
| Middleware | Auth Required | Admin Required | Localhost OK |
|---|---|---|---|
authenticateToken |
Yes | No | No |
authenticateAdmin |
Yes | Yes | No |
authenticateTokenOrLocalhost |
Yes | No | Yes (bypasses) |
authenticateApiKey |
API key | No | No |
authenticateApiKeyOrLocalhost |
API key | No | Yes (bypasses) |
Route Protection Summary
| Route Group | Middleware | Access Level |
|---|---|---|
/api/auth/login,register,forgot-password,reset-password |
None | Public |
/api/auth/user,change-password,logout |
authenticateToken |
Authenticated |
/api/files/*, /api/docs/*, /api/git/* |
authenticateToken |
Authenticated |
/api/skills/*, /api/scheduler/* |
authenticateToken |
Authenticated |
/api/meetings/* |
authenticateToken |
Authenticated |
/api/profile/* |
authenticateToken |
Authenticated |
/api/setup/* |
authenticateToken |
Authenticated |
/api/admin/* |
authenticateAdmin |
Admin only |
/api/admin/cloud-connections/* |
authenticateAdmin |
Admin only |
/api/admin/cloud-mounts/* |
authenticateAdmin |
Admin only |
/api/v1/meetings/* |
authenticateApiKey |
API key |
/api/v1/projects/* |
authenticateApiKey |
API key |
/api/v1/tasks/* |
authenticateApiKeyOrLocalhost |
API key or localhost |
/api/cron/* |
localhost check | Localhost only |
/api/mcp/cli/list |
localhost check | Localhost only |
/api/mcp/:service/* |
authenticateToken |
Authenticated |
Session Management
Architecture Decision
Session management underwent a major overhaul (2025-01-24). Key changes:
- Removed: Database session validation layer
- Source of truth: Filesystem (Claude CLI session dirs), not browser storage
- Resilience: System survives browser data clearing and container restarts
- Permissions: Default to
bypassPermissions(all permissions on)
Session Lifecycle
- User sends first message → new Claude CLI process spawned
- Session ID generated (UUID)
- Session directory created on filesystem
- Subsequent messages reuse the same CLI process
- Session recorded in
sessionstable for analytics - No explicit session expiry (CLI process may be killed by timeout)
JWT Token Lifecycle
- Generated on login (no expiry set)
- Stored in client localStorage
- Sent on every API request
- Valid until server restart changes JWT_SECRET (if using default)
- No refresh token mechanism
- No token revocation mechanism
Tenant Model
Single-tenant per deployment. Each Docker container serves one organization. There is no tenant isolation within a single instance -- all users in a deployment share:
- The same database
- The same file system
- The same Claude CLI configuration
- The same knowledge base
- The same API keys and integrations
Admin Access
Admin users can:
- Create, edit, delete other users
- Configure AI providers (Claude direct, AWS Bedrock)
- View analytics and usage reports
- Manage cloud drive connections and mounts
- Configure MCP integrations
- Manage output styles
- Send welcome emails
- Refresh analytics stats
Admin is determined by is_admin column in users table. First registered user is automatically admin.
Security Considerations
Password Storage
- bcrypt with default salt rounds
- No password complexity requirements enforced server-side
- Password change requires current password
Token Security
- JWT secret should be set via
JWT_SECRETenv var - Default fallback secret exists for development (insecure in production)
- No token rotation or refresh mechanism
- No session invalidation on password change
API Key Security
- Keys hashed with bcrypt (irreversible)
- Full key shown once at creation, never retrievable again
- Keys can be deactivated via
is_activeflag - O(n) comparison on each request (scales poorly with many keys)
Rate Limiting
express-rate-limitmiddleware- Configurable:
RATE_LIMIT_MAX(default 100),RATE_LIMIT_WINDOW_MS(default 15 min) - Can be disabled:
RATE_LIMIT_ENABLED=false
Security Headers
- Helmet.js enabled by default (
HELMET_ENABLED=true) - Can be disabled for development
