7.6 KiB
Phase 2: Branch Middleware (ElysiaJS) - Checklist
✅ Overview
Implement branch validation middleware to enforce multi-tenant access control, integrate with Keycloak groups, and provide branch context to all routes.
📋 Completed Tasks
Middleware Implementation
- Create
src/middleware/branch.ts- Define BranchContext interface
- Define AccessibleBranch interface
- Implement branchMiddleware using Elysia's derive
- Add branch validation logic
- Add error handling for unauthorized access
- Add error handling for inactive branches
- Implement default branch selection
- Export helper functions (canAccessBranch, getDefaultBranch)
Type Safety
- Fix TypeScript type errors
- Correct database import path
- Make BranchContext extend Record<string, unknown>
- Handle nullable isActive field
Documentation
- Add comprehensive JSDoc comments
- Add usage examples
- Document TODOs for authentication integration
🎯 Key Features Implemented
1. Branch Context Injection
- ✅ Automatically injects branch context into all routes
- ✅ Provides
currentBranchId,currentBranchCode,userId - ✅ Exposes
accessibleBranchesfor UI controls - ✅ Exposes
userGroupsfor permission checks
2. Branch Access Validation
- ✅ Validates
x-branch-idheader - ✅ Checks user's Keycloak groups
- ✅ Prevents cross-branch access
- ✅ Blocks inactive branches
3. Error Handling
- ✅ Clear error messages for unauthorized access
- ✅ Helpful error for missing branch access
- ✅ Inactive branch blocking
4. Helper Functions
- ✅
canAccessBranch()- Check if user can access specific branch - ✅
getDefaultBranch()- Get user's default branch - ✅
getUserAccessibleBranches()- Fetch accessible branches from DB
📊 Middleware Flow
Request
↓
Extract User ID & Groups (JWT/Session)
↓
Get Accessible Branches from DB
↓
Check x-branch-id Header
↓
Validate Branch Access
↓
Inject Branch Context
↓
Route Handler
🔧 Usage Examples
Basic Usage
import { Elysia } from "elysia";
import { branchMiddleware } from "@/middleware/branch";
const app = new Elysia()
.use(branchMiddleware)
.get("/customers", async ({ currentBranchId, userId }) => {
// currentBranchId is automatically available
const customers = await getCustomersByBranch(currentBranchId);
return customers;
});
Branch-Specific Operations
app.get("/quotations/:id", async ({ params, currentBranchId }) => {
const quotation = await getQuotationById(params.id, currentBranchId);
if (!quotation) {
throw new Error("Quotation not found or access denied");
}
return quotation;
});
Multi-Branch UI Support
app.get("/api/me/branches", async ({ accessibleBranches }) => {
// Return all branches user can access for UI dropdown
return accessibleBranches;
});
Manual Access Check
import { canAccessBranch } from "@/middleware/branch";
app.post(
"/admin/transfer",
async ({ body, currentBranchId, accessibleBranches }) => {
const targetBranchId = body.targetBranchId;
if (!canAccessBranch(accessibleBranches, targetBranchId)) {
throw new Error("Cannot transfer to branch you don't have access to");
}
// Proceed with transfer
},
);
🚨 Important Notes
Authentication Integration (TODO)
The middleware currently has TODOs for proper authentication:
// TODO: Implement proper JWT/session extraction
function extractUserIdFromRequest(request: Request): string | null {
// Replace with actual JWT verification
const token = request.headers.get("authorization")?.replace("Bearer ", "");
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return decoded.userId;
}
This will be implemented in Phase 3: Keycloak Integration.
Header Requirement
All requests must include the x-branch-id header to specify the target branch:
curl -H "x-branch-id: <branch-uuid>" \
-H "authorization: Bearer <jwt-token>" \
http://localhost:3000/api/customers
If no header is provided, the middleware uses the user's first accessible branch as default.
Branch Inactivation
When a branch is marked as inactive (isActive: false):
- Users cannot switch to that branch
- Existing sessions on that branch will fail
- Data remains intact but inaccessible
🔍 Testing Checklist
Unit Tests (TODO)
- Test user extraction from JWT
- Test group extraction from JWT
- Test accessible branches fetching
- Test branch validation (valid branch)
- Test branch validation (invalid branch)
- Test branch validation (inactive branch)
- Test default branch selection
- Test error messages
Integration Tests (TODO)
- Test middleware with real Elysia app
- Test cross-branch access prevention
- Test multiple branch access
- Test header-based branch switching
- Test unauthorized user handling
📝 Known Limitations
-
Authentication Not Yet Implemented
- Current implementation returns
nullfor user ID - Must be completed in Phase 3
- Testing requires mock authentication
- Current implementation returns
-
Branch Group Mapping Hardcoded
- Currently maps
["alla", "onvalla"] - Could be made configurable
- Consider dynamic branch group discovery
- Currently maps
-
Performance
- Fetches branches on every request
- Consider caching accessible branches
- Could store in session/JWT
🔄 Next Steps
Immediate (Phase 2)
- Write unit tests
- Write integration tests
- Add logging/middleware
- Document API changes
Phase 3: Keycloak Integration
- Implement JWT verification
- Implement user ID extraction
- Implement group extraction
- Add token refresh logic
- Test with real Keycloak
Phase 5: Controllers Update
- Remove
/:branchpath parameters - Update all routes to use middleware context
- Add branch context to responses
- Update API documentation
📊 File Changes
Created Files
- ✅
src/middleware/branch.ts(205 lines)
Modified Files
- None yet (controllers will be updated in Phase 5)
Documentation Files
- ✅
docs/checklist-phase2-middleware.md
✨ Success Criteria
- Middleware validates branch access
- Prevents cross-branch data access
- Provides branch context to routes
- Handles inactive branches
- Returns clear error messages
- TypeScript types are correct
- Helper functions work correctly
- Documentation is complete
- Unit tests pass
- Integration tests pass
- JWT authentication works (Phase 3)
🎯 Security Considerations
-
Branch Isolation ✅
- Users can only access their assigned branches
- Cross-branch requests are blocked
-
Header Validation ✅
- Validates branch exists and is active
- Checks user has permission
-
Session Security (Pending Phase 3)
- JWT tokens must be verified
- Token expiration must be checked
- Revoked tokens must be rejected
-
Error Messages ✅
- Don't expose internal structure
- Don't reveal other branches
- Generic "access denied" for security
📚 References
Phase 2 Status: ✅ CORE COMPLETED (Tests Pending) Completion Date: 2026-04-23 Next Phase: Phase 3 - Keycloak Integration Blocking: Tests, Phase 3 (Authentication)