4.1 KiB
identity
Purpose
Defines the identity model: users (authentication records) and persons (human identity records) with UUIDv7 primary keys.
Requirements
Requirement: Split identity model
The system SHALL maintain two distinct identity tables: users for authentication identity and persons for business identity. A users record represents how an actor authenticates (OIDC subject, login state). A persons record represents who an actor is in the business domain (name, email, status). The two are linked by persons.user_id → users.user_id.
Scenario: New user authenticates via OIDC
- WHEN a user completes OIDC authentication and no
usersrecord exists for their OIDC subject - THEN the system SHALL create a
usersrecord with the OIDC subject and anactivestatus - AND the system SHALL create a
personsrecord linked to that user, populated with display name and email from the OIDC claims
Scenario: Returning user authenticates
- WHEN a user completes OIDC authentication and a
usersrecord already exists for their OIDC subject - THEN the system SHALL update
last_login_atandlast_login_ipon theusersrecord - AND the system SHALL update the
personsrecord if the display name or email from OIDC claims has changed
Requirement: Users table schema
The users table SHALL contain the following fields: user_id (UUIDv7 primary key, DEFAULT uuidv7()), oidc_subject (TEXT, UNIQUE, NOT NULL), status (VARCHAR(20), NOT NULL, DEFAULT 'active'), last_login_at (TIMESTAMPTZ), last_login_ip (INET), created_at (TIMESTAMPTZ, NOT NULL, DEFAULT NOW()), updated_at (TIMESTAMPTZ, NOT NULL, DEFAULT NOW()).
Scenario: User ID is a UUIDv7
- WHEN a new
usersrecord is inserted without an explicituser_id - THEN the database SHALL generate a UUIDv7 via the
uuidv7()function
Scenario: OIDC subject uniqueness
- WHEN an attempt is made to insert a
usersrecord with anoidc_subjectthat already exists - THEN the database SHALL reject the insert with a unique constraint violation
Requirement: Persons table schema
The persons table SHALL contain the following fields: person_id (UUIDv7 primary key, DEFAULT uuidv7()), user_id (UUID, FK → users, UNIQUE), display_name (VARCHAR(255), NOT NULL), primary_email (VARCHAR(255), NOT NULL), primary_email_verified (BOOLEAN, NOT NULL, DEFAULT FALSE), status (VARCHAR(20), NOT NULL, DEFAULT 'active'), created_at (TIMESTAMPTZ, NOT NULL, DEFAULT NOW()), updated_at (TIMESTAMPTZ, NOT NULL, DEFAULT NOW()).
Scenario: Person linked to user
- WHEN a
personsrecord is created - THEN it SHALL reference exactly one
usersrecord viauser_id - AND the
user_idvalue SHALL be unique across allpersonsrecords (1:1 relationship)
Scenario: Person survives user deactivation
- WHEN a
usersrecord has itsstatusset todeactivated - THEN the corresponding
personsrecord SHALL remain withstatus = 'active'(business identity persists independently of auth state)
Requirement: Identity module owns its schema
The identity module SHALL own its database schema via migrations in internal/identity/migrations/. The identity module SHALL own its queries via sqlc configuration in internal/identity/sqlc.yaml generating into the internal/identity/ Go package.
Scenario: Identity migration creates tables
- WHEN the identity module's migration runs against an empty database
- THEN the
usersandpersonstables SHALL exist with all specified columns, constraints, and indexes
Scenario: Identity sqlc generates typed queries
- WHEN
sqlc generateis run from the identity module - THEN it SHALL produce Go types and query methods for
usersandpersonsin theidentitypackage
Requirement: Updated at trigger
The system SHALL automatically update the updated_at timestamp on users and persons rows whenever they are modified.
Scenario: User record updated
- WHEN any field on a
usersrecord is modified - THEN the
updated_atfield SHALL be set to the current timestamp via a database trigger