Document Type: Domain Design (Tier 2)
Domain: USERMGMT
Domain Character: Stateful
SRS Reference: user-management.md
Status: Draft
Last Updated: 2026-01-25
1. Overview
1.1 Purpose
The User Management subsystem provides complete user lifecycle management including account administration, authentication, session control, and role-based access enforcement. It is the identity backbone of the system, controlling who can access the application and what actions they can perform.
This domain is characterized by:
- Stateful user lifecycle with distinct states (active, blocked, pending email verification)
- Hybrid authentication via AWS Cognito (native credentials or federated SAML)
- Session management with single-device enforcement and inactivity timeout
- Five-tier role hierarchy (Junior, Senior, Client-Admin, Manager, Super Admin)
- Multi-site access control for Manager and Super Admin roles
- Audit trail integration for all security-relevant actions
1.2 Requirements Covered
| REQ ID | Title | Category |
|---|
| REQ-USERMGMT-001 | Display User Accounts | Administration |
| REQ-USERMGMT-002 | Modify User Role | Administration |
| REQ-USERMGMT-003 | Reset User Password | Administration |
| REQ-USERMGMT-004 | Configure User MFA | Administration |
| REQ-USERMGMT-005 | Request Email Verification | Administration |
| REQ-USERMGMT-006 | Control User Account Status | Administration |
| REQ-USERMGMT-007 | Delete User Account | Administration |
| REQ-USERMGMT-008 | Create User Account | Administration |
| REQ-USERMGMT-009 | Authenticate Users | Authentication |
| REQ-USERMGMT-010 | Verify Email on First Login | Authentication |
| REQ-USERMGMT-011 | Manage User Sessions | Session |
| REQ-USERMGMT-012 | Enforce Access Control Policies | Access Control |
| REQ-USERMGMT-013 | Enforce Role-Based Access Control | Access Control |
| REQ-USERMGMT-014 | Provide Multi-Site Data Access | Access Control |
| REQ-USERMGMT-015 | Enforce Authentication Security Policies | Security |
| REQ-USERMGMT-016 | Ensure System Readiness Before Login | System |
1.3 Constraints
Tier 2 Constraint: This document describes ownership, patterns, and design rationale. Authentication architecture details are in SDS: Security Architecture. It does not duplicate Cognito configuration or password policies documented in the SRS acceptance criteria.
1.4 Dependencies
| Direction | Domain/Component | Purpose |
|---|
| Provides to | All domains | Authenticated user context via middleware |
| AUDIT | User lifecycle events |
| Multi-site screens | Manager multi-site filtering |
| Consumes | AWS Cognito | Identity management, credential verification |
| AWS SES | Email notifications and verification |
| DynamoDB | Session storage (production) |
2. Component Architecture
2.1 Component Diagram
2.2 Component Responsibilities
| Component | Type | Responsibility | REQ Trace |
|---|
LoginController | Controller | Native login flow (username/password) | REQ-USERMGMT-009, 011 |
SamlLoginController | Controller | Federated SAML SSO via Cognito | REQ-USERMGMT-009 |
MfaController | Controller | MFA setup and TOTP verification | REQ-USERMGMT-004, 009 |
ChangePasswordController | Controller | Mandatory password change flow | REQ-USERMGMT-003, 009 |
ResetPasswordController | Controller | Forgot password flow | REQ-USERMGMT-003 |
UsersController | Controller | User CRUD operations | REQ-USERMGMT-001, 002, 007, 008 |
BlockedUsersController | Controller | User enable/disable operations | REQ-USERMGMT-006 |
HandleAuthenticated | Trait | Shared auth logic (MFA challenge, session, access control) | REQ-USERMGMT-009, 011, 012 |
CognitoAuthenticator | Service | Cognito adminInitiateAuth wrapper | REQ-USERMGMT-009 |
CognitoUserManager | Service | Cognito user lifecycle operations | REQ-USERMGMT-003-008 |
StoreUserAction | Action | Create user with Cognito sync | REQ-USERMGMT-008 |
UpdateUserTypeAction | Action | Update user role with validation | REQ-USERMGMT-002 |
BlockUserAction | Action | Disable user and revoke sessions | REQ-USERMGMT-006 |
UnblockUserAction | Action | Enable previously disabled user | REQ-USERMGMT-006 |
2.3 Architectural Patterns
Pattern: Cognito-Synchronized State
All user state changes are synchronized to AWS Cognito:
Local Operation → Database Update → Cognito API Call → Audit Event
This ensures identity state consistency between local database and identity provider.
Pattern: Trait-Based Controller Composition
Authentication controllers share common logic via the HandleAuthenticated trait, reducing duplication while allowing controller-specific customization.
Pattern: Action-Based Business Logic
Business logic is encapsulated in Action classes, keeping controllers thin and logic testable:
// Controller delegates to action
$user = $storeUser(auth()->user(), $request->getStoreUserData());
3. Data Design
3.1 Entity Ownership
This domain owns the following entities:
| Entity | Table | Purpose | Key Relationships |
|---|
User | users | User account data | -> sites (visible_sites), -> logged_in_site |
UserVisibleSite | user_visible_sites | Site access mapping | -> user, -> site |
3.2 Entity Relationship Overview
3.3 User Type Enumeration
| Value | Name | Description |
|---|
| 1 | JUNIOR | Base role with Run Files, Reports, Audits, Upload access |
| 2 | SENIOR | Junior + Westgard settings management |
| 3 | CLIENT_ADMIN | User Management and Audits only |
| 4 | SUPER_ADMIN | Full system access |
| 5 | MANAGER | Multi-site read-only access |
3.4 Role Permission Matrix
| Permission | Junior | Senior | Client-Admin | Manager | Super Admin |
|---|
| View Run Files | Yes | Yes | No | Read | Yes |
| View Reports | Yes | Yes | No | Read | Yes |
| View Audits | Yes | Yes | Yes | Read | Yes |
| Upload Runs | Yes | Yes | No | No | Yes |
| Westgard Settings | No | Yes | No | No | Yes |
| User Management | No | No | Yes* | No | Yes |
| Multi-Site Access | No | No | No | Yes | Yes |
| Create Comments/Alerts | Yes | Yes | No | Yes | Yes |
| Modify Results | Yes | Yes | No | No | Yes |
*Client-Admin cannot modify Super Admin accounts.
4. Interface Design
4.1 APIs Provided
| Endpoint | Method | Purpose | REQ Trace |
|---|
/login | POST | Native authentication | REQ-USERMGMT-009 |
/login/saml | GET | Initiate SAML SSO | REQ-USERMGMT-009 |
/logout | POST | Session termination | REQ-USERMGMT-011 |
/api/users | GET | List users with filters | REQ-USERMGMT-001 |
/api/users | POST | Create user account | REQ-USERMGMT-008 |
/api/users/{id} | PUT | Update user attributes | REQ-USERMGMT-002 |
/api/users/{id} | DELETE | Delete disabled user | REQ-USERMGMT-007 |
/api/blocked-users | POST | Disable user account | REQ-USERMGMT-006 |
/api/blocked-users/{id} | DELETE | Enable user account | REQ-USERMGMT-006 |
/change-password | GET/POST | Mandatory password change | REQ-USERMGMT-003, 009 |
/mfa-setup | GET | MFA setup screen | REQ-USERMGMT-004 |
/verify-mfa | POST | Verify TOTP code | REQ-USERMGMT-004, 009 |
4.2 APIs Consumed
| Service | Purpose | REQ Trace |
|---|
| AWS Cognito | Identity management, credential verification | REQ-USERMGMT-003-009 |
| AWS SES | Email verification, notifications | REQ-USERMGMT-005, 008 |
4.3 Events Published
| Event | Payload | Purpose | Listener |
|---|
UserLoggedIn | user_id, timestamp | Audit trail | LogIntoDatabase |
UserLoggedOut | user_id, timestamp | Audit trail | LogIntoDatabase |
UserAccountCreated | user_id, creator_id | Audit trail | LogIntoDatabase |
UserAccountDeleted | user_id, deleter_id | Audit trail | LogIntoDatabase |
UserAccountDisabled | user_id, modifier_id | Audit + real-time logout | LogIntoDatabase, BroadcastDisable |
UserAccountEnabled | user_id, modifier_id | Audit trail | LogIntoDatabase |
UserAssociatedWithRole | user_id, old_role, new_role | Audit trail | LogIntoDatabase |
PasswordChanged | user_id, timestamp | Audit trail | LogIntoDatabase |
5. Behavioral Design
5.1 User Lifecycle State Machine
5.2 Authentication Flow
Algorithm: Authenticate User
Inputs:
- credentials: username/password OR SAML assertion
- auth_type: "native" | "federated"
Outputs:
- result: AuthResult (success with session, or challenge required)
Assumptions:
- AWS Cognito is accessible
- User pool is configured
Steps:
1. If auth_type == "native":
a. Call CognitoAuthenticator.authenticate(credentials)
b. If PASSWORD_CHANGE challenge: redirect to change-password
c. If MFA_SETUP challenge: redirect to mfa-setup with secret
d. If SOFTWARE_TOKEN_MFA challenge: redirect to mfa-verify
e. If success: proceed to step 3
2. If auth_type == "federated":
a. Redirect to Cognito hosted UI
b. Cognito returns access_token via callback
c. Proceed to step 3
3. Fetch Cognito user attributes using access_token
4. Validate access control:
a. If multi-site enabled and no valid visible sites: reject
b. If site requires Cognito group and user lacks group: reject
5. Create or update local user (UpdateOrCreateLocalUser)
6. Mark email as verified in Cognito
7. Login to Laravel session (auth()->login)
8. Terminate other sessions (logoutOtherDevices)
9. Emit UserLoggedIn event
10. Redirect to application
Notes:
- SAML users are auto-provisioned with Senior role
- Session termination enforces single-device policy
5.3 User Creation Flow
Algorithm: Create User Account
Inputs:
- actor: Authenticated admin user
- data: StoreUserData (username, email, role, visible_sites)
Outputs:
- user: Created User entity
Assumptions:
- Actor has permission to create users
- Email and display name are unique
Steps:
1. Begin database transaction
2. Create User record:
- username (lowercased)
- email
- user_type (defaults to JUNIOR)
- display_name
- cognito_group
3. Sync visible sites relationship
4. Call CognitoUserManager.createCognitoUser(user)
5. Commit transaction
6. Emit UserAccountCreated event
7. Emit UserAssociatedWithRole event
8. Return created user
Notes:
- On Cognito failure, transaction rolls back
- Cognito sends password setup email to user
5.4 Account Disable Flow
Algorithm: Disable User Account
Inputs:
- user: User to disable
- actor: Authenticated admin user
Outputs:
- user: Disabled User entity
Assumptions:
- Actor has permission to disable users
- User is not currently online (unless force_block)
Steps:
1. Set user.blocked = true
2. Revoke all user tokens (user.tokens.each.revoke())
3. Emit UserAccountDisabled event
4. Call CognitoUserManager.disableCognitoUser(user)
5. Return disabled user
Notes:
- Disabled users cannot authenticate
- Active sessions are terminated via token revocation
- Cognito disable prevents federated login
5.5 MFA Enrollment Flow
5.6 Role Assignment Logic
Algorithm: Update User Role
Inputs:
- actor: Admin user performing change
- user: Target user
- new_role: Role to assign
Outputs:
- void (updates user in place)
Assumptions:
- Actor has permission based on ALLOWED_TYPES_FOR_TYPE matrix
Steps:
1. Validate actor can assign new_role:
- JUNIOR can only assign JUNIOR
- SENIOR can only assign SENIOR
- CLIENT_ADMIN can assign MANAGER, CLIENT_ADMIN, SENIOR, JUNIOR
- MANAGER can assign MANAGER, CLIENT_ADMIN, SENIOR, JUNIOR
- SUPER_ADMIN can assign any role
- No one can assign SUPER_ADMIN except SUPER_ADMIN
2. If validation fails: throw ValidationException
3. Store old role for audit
4. Update user.user_type = new_role
5. Call CognitoUserManager.updateCognitoUserAttributes(user)
6. If role changed: emit RoleChangedForUser event
Notes:
- CLIENT_ADMIN cannot modify SUPER_ADMIN accounts
- Role changes are synchronized to Cognito custom:UserGroup attribute
5.7 Password Policy Decision Table
| Condition | Password Expiry | History Check | Complexity Check | Action |
|---|
| New account | N/A | N/A | Yes | Cognito enforces on creation |
| Admin reset | N/A | N/A | Yes | Force change on next login |
| Self-change | Reset timer | Yes (5 previous) | Yes | Accept if valid |
| Expired (90 days) | Yes | N/A | N/A | Force change on login |
| Reused password | N/A | Fail | N/A | Reject with error |
Precedence: Complexity check always runs. History check only on self-change.
Default: If no condition matches, proceed with normal authentication.
6. Error Handling
| Condition | Detection | Response | User Impact |
|---|
| Invalid credentials | Cognito AuthenticationException | Return to login with error | "Authentication error" message |
| Account disabled | Cognito UserDisabledException | Return to login with error | Cannot login |
| Account locked (5 attempts) | Cognito NotAuthorizedException | Return to login with error | Must contact admin |
| Invalid MFA code | Cognito CodeMismatchException | Return to MFA screen | Can retry |
| Expired MFA session | Cognito ExpiredCodeException | Return to login | Must start over |
| No valid visible sites | getCognitoError check | Return to login with error | "No valid visible sites" |
| Invalid Cognito group | getCognitoError check | Return to login with error | "Invalid user group" |
| Delete enabled user | Controller check | HTTP 403 Forbidden | Must disable first |
| Concurrent modification | Database constraint | HTTP 409 Conflict | Refresh and retry |
7. Configuration
| Setting | Location | Default | Effect | REQ Trace |
|---|
session_inactivity_timeout_minutes | client_configurations | 30 | Inactivity timeout | REQ-USERMGMT-011 |
prevent_native_login | config/auth.php | false | Hide native login form | REQ-USERMGMT-009 |
require_cognito_group | client_configurations | false | Require Cognito group | REQ-USERMGMT-012 |
block_unauthorized_ip | client_configurations | false | Enable IP whitelist | REQ-USERMGMT-012 |
password_expiry_days | Cognito config | 90 | Days until password expires | REQ-USERMGMT-015 |
password_history_count | Cognito config | 5 | Previous passwords blocked | REQ-USERMGMT-015 |
max_login_attempts | Cognito config | 5 | Lockout threshold | REQ-USERMGMT-015 |
use_multiple_sites | features | varies | Enable multi-site mode | REQ-USERMGMT-014 |
See Configuration Reference for full list.
8. Implementation Mapping
8.1 Code Locations
| Component | Type | Path |
|---|
| LoginController | Controller | app/Http/Controllers/Auth/LoginController.php |
| SamlLoginController | Controller | app/Http/Controllers/Auth/SamlLoginController.php |
| MfaController | Controller | app/Http/Controllers/Auth/MfaController.php |
| ChangePasswordController | Controller | app/Http/Controllers/Auth/ChangePasswordController.php |
| ResetPasswordController | Controller | app/Http/Controllers/Auth/ResetPasswordController.php |
| UsersController | Controller | app/Http/Controllers/UsersController.php |
| BlockedUsersController | Controller | app/Http/Controllers/BlockedUsersController.php |
| HandleAuthenticated | Trait | app/Http/Controllers/Auth/HandleAuthenticated.php |
| CognitoAuthenticator | Service | app/Support/CognitoAuthenticator.php |
| CognitoUserManager | Service | app/CognitoUserManager.php |
| User | Model | app/User.php |
| StoreUserAction | Action | app/Actions/UserManagement/StoreUserAction.php |
| UpdateUserTypeAction | Action | app/Actions/UserManagement/UpdateUserTypeAction.php |
| BlockUserAction | Action | app/Actions/Users/BlockUserAction.php |
| UnblockUserAction | Action | app/Actions/Users/UnblockUserAction.php |
| UpdateUserAction | Action | app/Actions/Users/UpdateUserAction.php |
8.2 Requirement Traceability
| REQ ID | Design Section | Primary Code |
|---|
| REQ-USERMGMT-001 | SS4.1 | UsersController.index, ListUsersAction |
| REQ-USERMGMT-002 | SS5.6 | UpdateUserTypeAction |
| REQ-USERMGMT-003 | SS5.2 | ChangePasswordController, CognitoUserManager.changeCognitoUserPassword |
| REQ-USERMGMT-004 | SS5.5 | MfaController, User.enableMfa, CognitoUserManager.enableMfaOfCognitoUser |
| REQ-USERMGMT-005 | SS5.3 | CognitoUserManager.makeEmailAsVerifiedOfCognitoUser |
| REQ-USERMGMT-006 | SS5.1, SS5.4 | BlockUserAction, UnblockUserAction, CognitoUserManager.disableCognitoUser |
| REQ-USERMGMT-007 | SS5.1 | UsersController.destroy, User.remove |
| REQ-USERMGMT-008 | SS5.3 | StoreUserAction, CognitoUserManager.createCognitoUser |
| REQ-USERMGMT-009 | SS5.2 | LoginController, SamlLoginController, HandleAuthenticated |
| REQ-USERMGMT-010 | SS5.2 | HandleAuthenticated.authenticated |
| REQ-USERMGMT-011 | SS5.2 | HandleAuthenticated.logoutOtherDevices, LoginController.logout |
| REQ-USERMGMT-012 | SS5.2 | HandleAuthenticated.getCognitoError |
| REQ-USERMGMT-013 | SS3.4 | UserType middleware, User.ALLOWED_TYPES_FOR_TYPE |
| REQ-USERMGMT-014 | SS3.4 | User.visibleSites, multi-site filtering |
| REQ-USERMGMT-015 | SS5.7 | Cognito password policies, audit events |
| REQ-USERMGMT-016 | - | Frontend health check (not backend implementation) |
9. Design Decisions
| Decision | Rationale | Alternatives Considered |
|---|
| AWS Cognito for identity | Managed service reduces security burden, supports SAML | Self-hosted (rejected: maintenance overhead) |
| Cognito tokens not persisted | Reduces attack surface, simplifies token management | Store tokens (rejected: complexity, rotation burden) |
| Single-device enforcement | Security requirement, prevents session sharing | Allow concurrent (rejected: security risk) |
| Soft-delete for users | Preserve audit history, allow recovery | Hard delete (rejected: loses history) |
| Role hierarchy in code | Simple matrix, easy to understand | Database-driven (rejected: over-engineering) |
| Synchronous Cognito sync | Consistency, simpler error handling | Async queue (rejected: eventual consistency issues) |
| Trait-based controller composition | Code reuse without inheritance hierarchy | Base class (rejected: inflexible), duplication (rejected: maintenance) |