Users
Login accounts for Renewa One. A users row is one authentication method, not a person’s identity: the durable identity is the contact (users.contactId, mandatory). Users authenticate via Authentication and are authorized via RBAC Authorization.
Human FK rule (spec
docs/superpowers/specs/2026-04-24-contact-as-sole-human-identity-design.md): FKs that point at a human (attribution, assignment, membership, audit) referencecontacts.id, neverusers.id. “Is this person an active user?” is a predicate (SELECT 1 FROM users WHERE contact_id = $1 AND is_active = true), not a separate identity. Legacy auth-subsystem FKs (sessions,*_tokens) are grandfathered until Phase 4. Writes useactor.contact.id(ServiceActor).
Source Files
| Layer | Path |
|---|---|
| Schema | backend/src/db/schema.ts (users) |
| Routes | backend/src/routes/users.ts |
| Auth Service | backend/src/services/auth-service.ts |
| Profile Page | frontend/src/pages/Profile.tsx |
| Profile Sections | frontend/src/pages/ProfilePersonalInfoSection.tsx, ProfilePasswordSection.tsx, ProfileTwoFactorSection.tsx, ProfileResponsibilitySection.tsx, ProfileDeleteAccountSection.tsx |
| Admin | frontend/src/pages/admin/UsersAdmin.tsx |
| Auth Store | frontend/src/store/auth.ts (Zustand) |
Database Tables
| Table | Purpose |
|---|---|
users | Main entity — email, type, role, auth fields, contact link |
sessions | Session tokens with rotation and expiry (grandfathered users.id FK) |
user_skills | M2M linking users to skills with experience levels |
user_preferred_partners | Preferred collaboration partners per user (bidirectional, isPrimary per department) |
user_departmentswas removed (RNW-336): department placement iscontacts.departmentIdon Contacts, populated by the Entra profile sync — see Departments.
Key Fields
| Field | Type | Notes |
|---|---|---|
email | varchar(255) | Login email (unique, required) |
passwordHash | text | Argon2id hash via Bun.password.hash() — nullable (SSO-only accounts have none) |
userType | enum | internal or external |
internalRole | enum | admin or employee (only for internal users, check-constrained) |
contactId | uuid FK | Mandatory link to Contacts — the durable identity (cascade) |
avatarUrl | varchar | Profile picture URL |
language | varchar(2) | UI language preference (de/en) |
isActive | boolean | Account active flag (the “active user” predicate) |
entraId | varchar | Microsoft Entra ID for SSO (unique) |
authProvider | enum | local or entra |
entraLastSyncedAt | timestamp | Last Entra profile sync |
emailVerified + token/expiry fields | bool/text | Email verification, password reset, magic link flows |
teamRole | enum | admin, team_lead, member — team role within departments (parallel to internalRole) |
skills | jsonb | pro/basic skill array (responsibilities system) |
defaultSupportType | enum | pro or basic — default support type for quick assignment |
preferredManagerId | uuid FK | Self-reference for escalation routing |
responsibilityView* | jsonb | Per-user view filter preferences (owner/departments/assignee) |
twoFactorSecret / twoFactorEnabled | text/bool | TOTP 2FA |
firstName, lastName, phone, company, position | varchar | Deprecated — kept temporarily; personal info lives on the contact |
User Types
| Type | Internal Role | Description |
|---|---|---|
internal | admin | Full system access, user management |
internal | employee | Standard internal user |
external | — | Craftsmen, partners, external collaborators |
Relationships
User *──1 Contact (durable identity; auth method keyed on contact_id)
User *──* Skills (via user_skills)
User *──* Users (preferred partners via user_preferred_partners)
User 1──* Sessions (grandfathered users.id FK)
User 1──* Buildings (creator, nullable set-null)
User 1──* Projects (creator, nullable set-null)
Department membership is not on the user — it lives on the contact (contacts.departmentId, Entra-synced). Attribution/assignment FKs across the schema point at the contact, not the user (Human FK rule).
Authentication Flow
- Login with email/password (argon2id verification via
Bun.password) — the login UI routes every@renewa.deemail to SSO - Optional 2FA via TOTP (
twoFactorSecret+ authenticator app) - JWT access token + refresh token stored in
sessionstable - Session rotation with previous-token grace period
- SSO via Microsoft Entra ID (
authProvider: 'entra', no password hash)
See Authentication for full details. Internal employees are Entra-owned: the hourly entra-sync job creates/updates their users and contacts (see Departments).
Frontend State
- Zustand store at
frontend/src/store/auth.tsmanages current user, token, and auth state - Profile page with five sections: personal info, password, 2FA, responsibility areas, account deletion
- Admin panel at
UsersAdmin.tsxfor user management (restricted to admins and team leads)
Features
- Hybrid RBAC — user type + internal role + per-resource roles (RBAC Authorization)
- 2FA support — TOTP-based two-factor authentication
- Entra-owned internal accounts — hourly sync creates/updates employees; SSO-only accounts have no password
- Skill tracking — user competencies via skills and levels; responsibilities system (
teamRole,skills,defaultSupportType) - Session management — JWT with refresh rotation and grace periods
Related Pages
Authentication | RBAC Authorization | Contacts | Departments | Admin Dashboard | Database Architecture | Service Layer Pattern