01 Context
The problem
Workroom organises a company’s departments, projects and tasks in one place. That only works if people trust it with sensitive work, which means each person sees the projects they are authorised to see and nothing else. Two things make this harder than it looks. Hiding things in the interface is not security, because the API can still be called directly. And the AI features run in a separate service, which is a second way for data to leak if it isn’t designed carefully.
- Access rules are enforced by the API, not only hidden in the UI.
- Every route that touches a project checks access, not only the list page.
- The AI service never sees more than the person asking could see.
- The rules live in one place, so the app and the API can’t disagree.
02 Design
The architecture and decisions
The main backend is Django with Django Ninja and Pydantic schemas. It owns authentication, roles and project membership. Admins, Managers and Employees have different permissions, and project membership decides which projects an Employee can see. The FastAPI AI service is separate. It only receives context that the main API has already filtered for the person making the request.
- Django and Django Ninja for the main API
- Pydantic for validated request and response schemas
- A separate FastAPI service for AI features
- Next.js and Tailwind CSS on the client
If the query can only return visible projects, a forgotten check can’t leak anything.
The AI service never holds broader access than the person asking, so there is one place where access is decided.
The UI and the API read the same rules, and changing a role means changing one table.
def visible_projects(user):
if user.role == Role.ADMIN:
return Project.objects.all()
return Project.objects.filter(
Q(members=user) | Q(department__managers=user)
).distinct()
@router.get("/projects/{project_id}/tasks", response=list[TaskOut])
def project_tasks(request, project_id: int):
project = visible_projects(request.auth).filter(id=project_id).first()
if project is None:
raise HttpError(403, "Not a member of this project")
return project.tasks.all()03 Implementation
The difficult parts
The authorization flow
Every request answers three questions in order. Is the person signed in? Does their role allow this action? Are they a member of this project, or do they have a role that sees the whole department? Only then does the handler run.
The permission matrix
Roles and actions are defined once, as a matrix of what Admins, Managers and Employees can do. The API enforces it, and the client reads the same rules to decide what to show, so a hidden button and a refused request always agree.
The boundary with the AI service
When someone asks the AI to plan work for a project, the request goes to the main API first. It checks access, builds the context from projects and tasks that person can see, and only then calls the AI service. The AI’s draft comes back through the main API and is saved only after the person reviews it.
Avoiding leaks through IDs
Guessing another project’s ID shouldn’t reveal anything about it. A project outside the visible set gets a refusal with no details, whichever route is used.
04 Validation
How I check it
Access control is only as good as its tests for the wrong person. The test suite is written from the point of view of someone who shouldn’t get in.
- A non-member gets a refusal on every route of a project, not only the list page.
- Requests with another project’s ID return nothing about that project.
- An AI request for a project the person can’t see is refused before the AI service is called.
- Removing someone from a project removes their access on their next request.
05 Reflection
What I learned and what comes next
Security belongs in the query
Starting every lookup from the set of visible projects made the safe path the easy path.
A second service needs a clear boundary
Treating the AI service as untrusted by default kept the design simple: it only knows what it’s given.
What comes next
An AI agent that breaks tasks into implementation steps, a second agent that can carry out some of them, such as generating documentation, and Kanban and timeline views.
