core PK: id 12 required 4 unique

Description

Core identity entity representing every authenticated person on the Meander platform — peer mentors, coordinators, organization admins, and global admins. Stores credentials, profile data, organizational affiliation, and account lifecycle state. Organization context is set at invitation time and cannot be changed at login. Deactivation is always soft (active flag) to preserve audit history.

21
Attributes
7
Indexes
8
Validation Rules
39
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Globally unique identifier for the user, used as the primary key across all foreign key relationships
PKrequiredunique
email string User's email address. Used as login identifier for email/password auth and as the delivery address for invitation and notification emails. Unique across the entire platform.
requiredunique
password_hash string bcrypt hash of the user's password. NULL for users who authenticate exclusively via BankID, Vipps, or passkey. Must never store plaintext.
-
first_name string User's given name, displayed throughout the UI and in peer mentor profiles shared with contacts
required
last_name string User's family name, displayed alongside first_name in lists, profiles, and coordinator views
required
phone string User's phone number in E.164 format. Used for SMS notifications and displayed on peer mentor profiles. Optional at signup but required for BankID/Vipps flows.
-
organization_id uuid Foreign key to the organization this user belongs to. Set at invitation time by an admin. Users cannot change this themselves. Drives all multi-tenancy scoping across the platform.
required
active boolean Soft-delete flag. FALSE means the user is deactivated and cannot log in. Deactivated users are never hard-deleted to preserve referential integrity in audit logs, activities, and approval history.
required
email_verified boolean Whether the user has confirmed their email address via the invitation activation link. Unverified users cannot log in.
required
invited_at datetime Timestamp when the invitation email was first dispatched. Used to detect stale invitations (e.g. expired after 72 hours).
-
activated_at datetime Timestamp when the user completed email verification and set their initial password. NULL until activation completes.
-
last_login_at datetime Timestamp of the most recent successful authentication (any method). Used in security monitoring and dormant account detection.
-
roles_updated_at datetime Timestamp updated whenever role assignments change. Mobile clients and the admin portal compare this against the JWT iat claim to trigger re-validation when roles have changed since the token was issued.
required
bankid_subject string The stable subject identifier returned by the BankID OIDC provider after successful authentication. Used to link subsequent BankID logins to the correct user record without storing personnummer in plaintext.
unique
vipps_subject string The stable subject identifier returned by Vipps Login OIDC after successful authentication. Used to link subsequent Vipps logins to the correct user record.
unique
personnummer_encrypted string AES-256-GCM encrypted Norwegian national identity number (personnummer) retrieved from BankID or Vipps during first OAuth login. Used to sync back to member systems that lack this field. Stored encrypted at rest, never logged or exposed via API.
-
preferred_language enum User's preferred app language. Drives locale selection for push notification templates, email content, and the mobile app UI.
required
profile_image_url string CDN URL of the user's uploaded profile photo. Used in peer mentor profiles shared with coordinators and contacts. NULL if the user has not uploaded a photo.
-
passkey_enabled boolean Whether the user has registered at least one FIDO2 passkey credential. Drives UI display of the passkey option at login.
required
created_at datetime Timestamp of user record creation. Set once at insert and never updated.
required
updated_at datetime Timestamp of the most recent update to any field on this record. Automatically maintained by an ON UPDATE trigger.
required

Database Indexes

idx_users_email
btree unique

Columns: email

idx_users_organization_id
btree

Columns: organization_id

idx_users_active_org
btree

Columns: organization_id, active

idx_users_bankid_subject
btree unique

Columns: bankid_subject

idx_users_vipps_subject
btree unique

Columns: vipps_subject

idx_users_roles_updated_at
btree

Columns: roles_updated_at

idx_users_last_login_at
btree

Columns: last_login_at

Validation Rules

email_format error

Validation failed

email_unique_platform_wide error

Validation failed

password_policy error

Validation failed

name_non_empty error

Validation failed

phone_e164_format error

Validation failed

organization_id_exists error

Validation failed

preferred_language_valid_enum error

Validation failed

profile_image_https_only error

Validation failed

Business Rules

organization_set_at_invitation
on_create

A user's organization_id is set when an admin sends the invitation and cannot be changed by the user. Users do not select their organization during login or onboarding — it is pre-determined by who invited them.

soft_delete_only
on_delete

Users must never be hard-deleted. Deactivation sets active=false. This preserves referential integrity for all historical activities, audit logs, expense approvals, and assignment records that reference the user ID.

role_escalation_prevention
on_update

An Org Admin cannot assign the Global Admin role. Only an existing Global Admin can grant Global Admin privileges. The Role Assignment Service enforces this by comparing the actor's highest role against the target role.

roles_updated_at_on_role_change
on_update

Whenever a user's role assignments change (add or revoke), roles_updated_at is bumped to NOW(). This signals mobile clients and the admin portal to re-validate their JWT, ensuring stale role claims are not honoured.

global_admin_no_org_context
always

Global Admin accounts have no organization_id affiliation and cannot access any organization's operational data by default. They manage the platform (system config, cross-org support) but not individual org content.

deactivated_user_cannot_login
always

Any authentication attempt (email/password, BankID, Vipps, passkey, biometric) for a user with active=false must be rejected with a non-specific error message that does not confirm the account exists.

unverified_user_cannot_login
always

A user whose email_verified=false cannot complete email/password login. The invitation link sets email_verified=true upon activation. BankID and Vipps flows set email_verified=true automatically on first successful auth.

Enforced by: Auth Backend Service
personnummer_encrypted_storage
on_create

The personnummer retrieved from BankID or Vipps OIDC claims must be encrypted using AES-256-GCM before persistence. It must never appear in application logs, API responses, or error messages.

invitation_token_expiry
on_create

Invitation tokens are time-limited (72 hours). An expired token must reject activation without creating or modifying the user record.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Components Managing This Entity

service Auth Backend Service ["backend"] service BankID OAuth Service ["mobile","backend"] service BankID OAuth Service ["mobile","backend"] service Vipps OAuth Service ["mobile"] service Vipps OAuth Service ["backend"] infrastructure JWT Claims Extractor ["mobile","backend"] service Permission Validation Middleware ["backend"] service Role Guard Service ["mobile"] service Role Guard Service ["mobile","backend"] service Profile Service ["mobile"] service Profile Service ["mobile"] service Share Service ["mobile"] service Share Service ["mobile"] service Passkey Auth Service ["mobile"] service Passkey Auth Service ["mobile","backend","shared"] service Proxy Scope Validator ["mobile"] ui Peer Mentor Selector Widget ["mobile"] service Stats Aggregation Service ["backend"] service Stats Aggregation Service ["backend","mobile"] service Team Stats Service ["mobile","backend"] service Team Stats Service ["backend","mobile"] service Certificate Service ["mobile","backend"] service Certificate Service ["mobile"] service Dashboard Service ["mobile"] service Dashboard Service ["mobile"] service KPI Access Control Service ["backend"] service KPI Aggregation Service ["backend"] service KPI Aggregation Service ["backend"] service Activity Feed Service ["backend"] service Activity Feed Service ["backend"] service Role Scope Resolver ["backend"] data User Repository ["backend"] data User Repository ["backend"] infrastructure Invitation Email Adapter ["backend"] service User Service ["backend"] service User Service ["backend"] data Role Repository ["backend"] service Role Assignment Service ["backend"] service Role Assignment Service ["backend"] service Bulk User Processing Service ["backend"] service Bulk User Processing Service ["backend"] service Report Access Control Service ["backend"] service Hierarchy Access Control Service ["backend"] service Security Monitoring Service ["backend"] service Security Monitoring Service ["backend"] service Session Service ["backend"] service Session Service ["backend"]