User
Data Entity
Description
Core identity record for every authenticated principal on the Meander platform. Covers all four roles — Peer Mentor, Coordinator, Org Admin, and Global Admin — across both the Mobile App and Admin Web Portal. Stores identity, credential metadata, invitation lifecycle, status, and push token. Authorization (which org, which role) is delegated to user_organization_roles. The auth module owns session and token state; this table owns the durable person record.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Surrogate primary key. Generated server-side (UUIDv4). Stable for the lifetime of the account, even across email changes. | PKrequiredunique |
email |
string |
Primary login credential and notification address. Lower-cased and trimmed before storage. Unique across the entire platform regardless of organization. | requiredunique |
email_verified |
boolean |
True once the user has clicked the verification link. Unverified accounts can receive invitations but cannot authenticate until verified. | required |
password_hash |
string |
Argon2id hash of the user's password. NULL for accounts that authenticate exclusively via BankID or Vipps (Phase 2). Never returned in API responses. | - |
first_name |
string |
User's given name. Displayed throughout the app and admin portal. Used in notification salutations and Bufdir report attribution. | required |
last_name |
string |
User's family name. Combined with first_name for display. Used in assignment dispatch, report attribution, and audit log actor labels. | required |
phone_number |
string |
Optional contact number, used for SMS notifications (Email/SMS Notifications feature) and Vipps-linked identity lookup. Stored in E.164 format. | - |
status |
enum |
Lifecycle state of the account. Active: normal operation. Paused: peer mentor temporarily inactive (Pause Function); account intact, excluded from public listings and assignment matching. Deactivated: admin-initiated, cannot log in. Suspended: platform-level lock, reserved for policy violations. | required |
is_global_admin |
boolean |
True for Norse Digital Products staff accounts. Global admins operate across all organizations from the Admin Web Portal and have no default access to any organization's operational data. This flag is separate from org-scoped roles stored in user_organization_roles. | required |
avatar_url |
string |
URL of the user's profile photo stored in cloud object storage. NULL if no photo uploaded. Used in profile screens, coordinator overviews, and admin portal user cards. | - |
preferred_language |
enum |
UI language preference. Drives app locale and notification templates. Defaults to Norwegian Bokmål. | required |
push_notification_token |
string |
FCM/APNs device token for the mobile app. Updated on each app launch. NULL for admin portal-only accounts (Org Admins and Global Admins who have not installed the mobile app). One token per user — multi-device is handled by updating this field on new login. | - |
biometric_enabled |
boolean |
Whether the user has enrolled biometric unlock (Face ID / fingerprint) on their primary device. The biometric credential itself is stored in the platform secure store on the device — this flag signals to the auth flow that biometric unlock is available. | required |
invitation_token |
string |
One-time token included in the invitation email link. Consumed on first login. NULL after the invitation is accepted or the token expires. | - |
invitation_expires_at |
datetime |
Timestamp after which the invitation_token is no longer valid. NULL for accounts that completed onboarding. Set to 7 days after invitation_sent_at. | - |
invited_by_user_id |
uuid |
FK to the user record of the Org Admin who sent the invitation. Used in audit trails and invitation management screens. NULL for seed/bootstrap accounts. | - |
onboarding_completed |
boolean |
True once the user has completed the first-run onboarding flow (accepted ToS, set password, verified email). Controls whether the app routes to onboarding screens on next launch. | required |
support_access_until |
datetime |
Timestamp until which a Global Admin has been granted time-bounded access to this user's organization data. Set by the Org Admin via Organization Settings. NULL means no active support access grant. The auth module and role guard check this field before granting cross-org access to Global Admins. | - |
last_login_at |
datetime |
Timestamp of the most recent successful authentication. Updated on every login event. Used by the Security Dashboard and session audit tooling. | - |
deactivated_at |
datetime |
Timestamp when the account was deactivated. NULL for active/paused accounts. Set on admin-initiated deactivation or bulk deactivation. | - |
deactivated_by_user_id |
uuid |
FK to the admin user who performed the deactivation. Part of the deactivation audit trail. NULL if account has never been deactivated. | - |
created_at |
datetime |
Record creation timestamp. Set once on INSERT. Used for Bufdir reporting period attribution and admin user list sorting. | required |
updated_at |
datetime |
Timestamp of the most recent modification to any field on this record. Maintained by an ON UPDATE trigger. Used by conflict resolution in the mobile sync pipeline. | required |
Database Indexes
idx_users_email
Columns: email
idx_users_status
Columns: status
idx_users_is_global_admin
Columns: is_global_admin
idx_users_invitation_token
Columns: invitation_token
idx_users_created_at
Columns: created_at
idx_users_last_login_at
Columns: last_login_at
idx_users_full_name
Columns: last_name, first_name
Validation Rules
email_format
error
Validation failed
password_strength
error
Validation failed
name_not_blank
error
Validation failed
phone_e164_format
error
Validation failed
status_transition_allowed
error
Validation failed
invitation_expiry_window
error
Validation failed
support_access_max_duration
error
Validation failed
avatar_url_https_only
error
Validation failed
Business Rules
unique_email_across_platform
Email addresses are unique across all organizations. A user invited by Org A who later joins Org B uses the same account — no duplicate records per email. Enforced at DB level (unique index) and API level before INSERT.
deactivated_user_cannot_authenticate
Users with status='deactivated' or status='suspended' are rejected at the auth layer before any token is issued. The auth-service checks status on every sign-in attempt, including biometric and BankID flows.
paused_mentor_excluded_from_listings
Peer mentors with status='paused' are excluded from the Geographic Map View, assignment matching candidates, and any public-facing coordinator overviews. Their account and data remain fully intact. Coordinators are notified when a mentor pauses.
global_admin_no_default_org_access
is_global_admin=true grants access to the Admin Web Portal globally but confers zero access to any organization's operational data (users, activities, contacts) unless support_access_until is set and in the future for that org. Role guard enforces this on every org-scoped API endpoint.
support_access_auto_expiry
When now() > support_access_until, Global Admin access to that organization's data is revoked immediately without any manual step. The role guard performs the check inline; no background job required. Every support session is written to the organization's audit log.
coordinator_peer_mentor_no_admin_portal_login
Users whose only roles (across all user_organization_roles records) are peer_mentor or coordinator are blocked from logging into the Admin Web Portal. The auth service returns a 403 with a redirect hint to the mobile app. is_global_admin=false is also required.
invitation_token_single_use_and_expiring
invitation_token is consumed (set to NULL) on first successful login. If invitation_expires_at has passed, the token is rejected and the Org Admin must re-invite. This prevents stale invite links from being used after an account is no longer expected.
certification_expiry_triggers_auto_pause
When a peer mentor's certification expires (tracked in certifications table), the expiry-check-service sets status='paused' automatically. The mentor is notified and the coordinator receives a warning. This implements the HLF requirement that expired-certificate mentors disappear from listings automatically.
bulk_deactivation_audit_required
Any bulk status change (deactivation, suspension) via bulk-user-service must record the acting admin's user ID in deactivated_by_user_id and write an audit log entry for each affected user. Individual deactivations via user-administration-service follow the same rule.