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) reference contacts.id, never users.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 use actor.contact.id (ServiceActor).

Source Files

LayerPath
Schemabackend/src/db/schema.ts (users)
Routesbackend/src/routes/users.ts
Auth Servicebackend/src/services/auth-service.ts
Profile Pagefrontend/src/pages/Profile.tsx
Profile Sectionsfrontend/src/pages/ProfilePersonalInfoSection.tsx, ProfilePasswordSection.tsx, ProfileTwoFactorSection.tsx, ProfileResponsibilitySection.tsx, ProfileDeleteAccountSection.tsx
Adminfrontend/src/pages/admin/UsersAdmin.tsx
Auth Storefrontend/src/store/auth.ts (Zustand)

Database Tables

TablePurpose
usersMain entity — email, type, role, auth fields, contact link
sessionsSession tokens with rotation and expiry (grandfathered users.id FK)
user_skillsM2M linking users to skills with experience levels
user_preferred_partnersPreferred collaboration partners per user (bidirectional, isPrimary per department)

user_departments was removed (RNW-336): department placement is contacts.departmentId on Contacts, populated by the Entra profile sync — see Departments.

Key Fields

FieldTypeNotes
emailvarchar(255)Login email (unique, required)
passwordHashtextArgon2id hash via Bun.password.hash()nullable (SSO-only accounts have none)
userTypeenuminternal or external
internalRoleenumadmin or employee (only for internal users, check-constrained)
contactIduuid FKMandatory link to Contacts — the durable identity (cascade)
avatarUrlvarcharProfile picture URL
languagevarchar(2)UI language preference (de/en)
isActivebooleanAccount active flag (the “active user” predicate)
entraIdvarcharMicrosoft Entra ID for SSO (unique)
authProviderenumlocal or entra
entraLastSyncedAttimestampLast Entra profile sync
emailVerified + token/expiry fieldsbool/textEmail verification, password reset, magic link flows
teamRoleenumadmin, team_lead, member — team role within departments (parallel to internalRole)
skillsjsonbpro/basic skill array (responsibilities system)
defaultSupportTypeenumpro or basic — default support type for quick assignment
preferredManagerIduuid FKSelf-reference for escalation routing
responsibilityView*jsonbPer-user view filter preferences (owner/departments/assignee)
twoFactorSecret / twoFactorEnabledtext/boolTOTP 2FA
firstName, lastName, phone, company, positionvarcharDeprecated — kept temporarily; personal info lives on the contact

User Types

TypeInternal RoleDescription
internaladminFull system access, user management
internalemployeeStandard internal user
externalCraftsmen, 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

  1. Login with email/password (argon2id verification via Bun.password) — the login UI routes every @renewa.de email to SSO
  2. Optional 2FA via TOTP (twoFactorSecret + authenticator app)
  3. JWT access token + refresh token stored in sessions table
  4. Session rotation with previous-token grace period
  5. 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.ts manages current user, token, and auth state
  • Profile page with five sections: personal info, password, 2FA, responsibility areas, account deletion
  • Admin panel at UsersAdmin.tsx for 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

Authentication | RBAC Authorization | Contacts | Departments | Admin Dashboard | Database Architecture | Service Layer Pattern