- Add admin-only users API: list/create/delete (prevent self-delete and last-admin deletion). - Include is_admin in auth responses. - Frontend: /admin/users page with table, selection, remove, Add User modal. - Show “Manage users” in user menu for admins and optional sidebar link. - Add i18n strings for admin UI. - Enhance create user script to grant admin via optional third arg. - Minor: set dev bootstrap user as admin in start script.
3 KiB
3 KiB
Authentication, Permissions, and Sharing Architecture
This document outlines the backend security model for authentication, authorization (RBAC), and resource sharing.
Authentication
- Session-based auth with
express-sessionand Sequelize store (backend/app.js). - Middleware
requireAuth(backend/middleware/auth.js) guards all API routes under/apiexcept health, login, andcurrent_user.
Resource Identity
- Core resources:
project,task,note. - Each resource has a stable
uidused for sharing/access decisions. Some routes accept numeric IDs but resolve touidinternally.
Authorization (RBAC)
- Access levels:
none,ro,rw,admin(permissionsService.ACCESS). - Ownership implies
rwaccess. Admin role impliesadminaccess (backend/services/rolesService.js). - Central check:
hasAccess(requiredAccess, resourceType, getResourceUid, options)(backend/middleware/authorize.js).- Resolves
uidviagetResourceUid(req). - Calls
permissionsService.getAccess(userId, resourceType, uid). - Compares levels and either
next()or returns 403/404 based on options.
- Resolves
Permissions Service
getAccess(userId, resourceType, uid):- If admin →
admin. - If owner (via model lookup) →
rw. - Else checks
permissionstable for shared access level.
- If admin →
ownershipOrPermissionWhere(resourceType, userId):- Returns a Sequelize
whereclause that matches owned resources or resources shared to the user (byuid). Useful for list endpoints.
- Returns a Sequelize
Sharing Model
- Stored in
permissionstable (backend/models/permission.js):- Columns:
user_id,resource_type,resource_uid,access_level(ro/rw/admin),propagation(direct/inherited),granted_by_user_id.
- Columns:
- Propagation rules computed by calculators (
backend/services/permissionsCalculators.js):- Sharing a
projectpropagates to descendanttasksandnotes(inherited). - Sharing a
taskcan propagate to its descendants. - Sharing a
noteapplies directly. - Revokes remove the corresponding rows (and inherited ones).
- Sharing a
HTTP Status Semantics
- 401 Unauthorized: no session or invalid session.
- 404 Not Found: resource does not exist (or explicitly configured legacy concealment).
- 403 Forbidden: resource exists but caller lacks required access.
Route Usage Patterns
- Read collection endpoints (e.g.,
/api/notes,/api/tasks,/api/projects):- Use
ownershipOrPermissionWhereto include owned and shared resources.
- Use
- Item endpoints (e.g.,
/api/note/:id,/api/task/:id):- Wrap handlers with
hasAccess('ro'|'rw', resourceType, getResourceUid, { notFoundMessage }). - Inside the handler, assume access is enforced; still handle true 404s when the item is deleted between checks.
- Wrap handlers with
Testing Guarantees
- Integration tests assert:
- 401 for unauthenticated requests.
- 404 for non-existent resources.
- 403 for existing resources without sufficient permissions.
- Project UID-slug routes correctly return 403 when the project exists but is not accessible.