Identity & access
Four tables. roles and users carry the RBAC model described in
RBAC; sessions are server-side rows behind a signed
cookie; guardian_links is the parent↔child join that makes scope own work.
| Table | Columns (keys) | Notes |
|---|---|---|
roles |
id PK · code UNIQUE · name · permissions (JSON text) |
permissions is map[resource]Permission{read, write, delete, scope} with a "*" wildcard key; the four built-ins get stable ids role-<code> and are seeded from Go at boot, never by SQL migrations |
users |
id PK · username UNIQUE · password_hash (bcrypt) · role_id FK→roles · full_name · phone · email · must_change_password (default 1) · disabled (default 0) · created_at |
teachers are users whose role is teacher; parents are users with guardian_links; the first-boot admin comes from ADMIN_USER/ADMIN_PASSWORD and is attached to manager |
sessions |
id PK · user_id · created_at · expires_at (indexed) |
24 h TTL; an expired row is rejected and deleted on read, and a sweeper purges at boot and hourly; the browser only holds the signed cookie |
guardian_links |
PK(parent_user_id, student_id) · FKs to users, students |
composite key — one parent cannot link the same child twice; the index on student_id serves the child→parents direction |
Relationships
roles 1 —< users 1 —< sessionsusers 1 —< guardian_links >— 1 students(parents at scopeownresolve to exactly these rows)
Ownership notes
users.id is referenced across the host — classes.teacher_id,
buses.caregiver_id, payments.employee_id, salaries.employee_id,
audit_log.user_id, created_by columns — which is why moving users into an
access plugin is a seam, not a toggle: see
Access.
Rendered from simple-lms/data-model/identity.md · OKF bundle · regenerate with npm run render