---
title: User self-registration and account management
summary: Creates, retrieves, updates, and post-processes external-user accounts and their child records.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.External.Users
reference_type: package
operation_id: OP-EU-01
trigger: Caller invokes the user factories, retrieval helpers, forgotten-user lookup, or last-logon update helpers.
---

# User self-registration and account management

This operation covers the `clsUser` lifecycle around external-user accounts: constructing a new account from `clsNewUserArgs`, resolving the first user type and contact linkage, persisting the `EXT_USER` row, saving child collections, reloading existing users, handling forgotten-user recovery, and updating the last-logon timestamp.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-EU-01 |
| **Trigger** | Registration/import creates a new `clsUser`; administration loads a user for edit; authentication updates last-logon; support flows call forgotten-user lookup |
| **Inputs** | `clsNewUserArgs`, `UserID`, AD user ID, email address, source-system identifiers, child user types, access groups, and security questions |
| **Outputs** | Persisted `EXT_USER` row, populated user aggregate, forgotten-user match, and updated last-logon timestamp |
| **Primary components** | `clsUser.InitializeContactAndCustomer`, `clsUser.AddUserType`, `clsUser.OnSave`, `clsUser.GetForgottenUser`, `clsUser.UpdateLastLogonDate` |
| **Entry point** | `Business\SAIF.Business.External.Users\clsUser.vb` |

## Steps

1. **Create or locate the root user object.** `NewUser` creates a fresh `clsUser` with a new `EXT_USER_SEQ` identifier, while `GetUser` loads an existing row by numeric user ID or AD user ID ([SQL-015](../sql/SQL-015-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user)).
2. **Initialize a new registration from `clsNewUserArgs`.** `InitializeContactAndCustomer` copies override audit fields, creates a temporary password through `clsPassword.Generate`, builds or reuses the contact/email/phone records, sets authorization and verification flags, and records test-user or injured-worker indicators ([BR-EU-01](../business-rules.md#br-eu-01-authorized-user-flag-persistence)).
   - If the request is not an import, it sets `EmailVerificationExpirationDate` and clears `AcceptedUserAgreement`.
   - If the request is a registration or import, it leaves `PasswordChangeRequired` off; other entry paths require a password change on first use.
3. **Resolve the primary user type and customer context.** `AddUserType` creates a `clsUserType`, assigns `UserID`, and derives `CustomerID`, `SourceSystemID`, and `SourceSystemGroupID` from the selected user type.
   - Agency users resolve customer info from the agent number.
   - Auditor users query `PA_AUDITOR` by AD user ID and vendor number, with the same test-user fallback used by the dedicated lookup path ([SQL-041](../sql/SQL-041-saif-business-external-users.md), [PA_AUDITOR](../database-tables.md#pa_auditor)).
   - Group, loss-control, and RTW users use `mdlGroupAssociation` helpers, including the `RTW_LOAD_CODE` lookup for RTW groups ([SQL-040](../sql/SQL-040-saif-business-external-users.md)).
   - Injured-worker variants translate the claim context into person and worker identifiers.
   - Medical-provider users resolve provider customer IDs and may auto-select provider access groups.
   - Policyholder users load policy data and auto-select the state-agency group when the policy entity code is `50`.
4. **Attach the contact relationship and access-group defaults.** When the resolved customer is available, `AddUserType` ensures the customer has an external-security contact relationship for this contact, applies the caller's selected access groups, and adds any system-controlled default groups from `clsUserAccessGroupCollection.GetAllUserTypeAccessGroups` ([SQL-023](../sql/SQL-023-saif-business-external-users.md), [SQL-024](../sql/SQL-024-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
5. **Derive the initial account status.** New registrations become Active for imports, Pending steward activation for agency or steward-governed registrations, and Pending email verification for all other registration flows.
6. **Validate uniqueness and required fields before persistence.** `IsValid` runs the child `clsNewUserArgs` rules for new users, then applies `clsUser` rules for names, email, phone, phone format, test-user expiry, and access-group combinations.
   - Duplicate active email detection queries other active non-test users by normalized email and blocks any matching type-and-group combination ([BR-EU-04](../business-rules.md#br-eu-04-unique-active-email-per-non-test-user), [SQL-016](../sql/SQL-016-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user), [EMAIL_ADDRESS](../database-tables.md#email_address), [EXT_USER_TYPE](../database-tables.md#ext_user_type)).
   - New users also require the AD user ID to be available before insert.
7. **Insert the `EXT_USER` row for a new account.** `BeforeInsert` sets create/update stamps, applies test-user expiration windows, and binds the account flags into the `EXT_USER` insert statement ([BR-EU-01](../business-rules.md#br-eu-01-authorized-user-flag-persistence), [SQL-005](../sql/SQL-005-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user)).
   - On insert row-count failure, the object records an insert error instead of marking the row clean.
8. **Update an existing `EXT_USER` row with optimistic concurrency.** `BeforeUpdate` refreshes the audit stamp, extends email-verification expiry when the email changed, and issues the `UPDATE ... AND update_num = :UpdateNum` statement ([BR-EU-01](../business-rules.md#br-eu-01-authorized-user-flag-persistence), [BR-EU-05](../business-rules.md#br-eu-05-optimistic-concurrency-via-update_num), [SQL-045](../sql/SQL-045-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user)).
   - On a zero-row update, the object reports a DB-contention error and leaves the aggregate dirty.
9. **Save dependent children around the root save.** `OnSave` persists a new customer/contact before the root row or saves a dirty existing contact, resets the user agreement if access groups changed, and calls the base save. `OnSaveComplete` then saves security questions, user types, and any deny-self-registration rows attached to the user aggregate.
10. **Reload existing users and their supporting projections.** Retrieval fills the base `EXT_USER` fields from [SQL-015](../sql/SQL-015-saif-business-external-users.md); later property access lazy-loads user types, security questions, and contacts.
    - `Types` loads user-type projections for the user and reattaches user-type rules.
    - `SecurityQuestions` loads the user's answers by user ID ([SQL-028](../sql/SQL-028-saif-business-external-users.md), [EXT_USER_SECURITY_QUESTION](../database-tables.md#ext_user_security_question)).
    - `GetProvider` loads provider-display information for an external user type ([SQL-018](../sql/SQL-018-saif-business-external-users.md), [EXT_USER_TYPE](../database-tables.md#ext_user_type)).
11. **Resolve forgotten-user requests through progressively broader identity matches.** `GetForgottenUser` first searches by the supplied source-system identifier and email, then retries by provider ID when the identifier is a provider tax ID, and finally retries by worker ID when the identifier is a valid claim number ([SQL-017](../sql/SQL-017-saif-business-external-users.md), [SQL-018](../sql/SQL-018-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user), [EMAIL_ADDRESS](../database-tables.md#email_address), [EXT_USER_TYPE](../database-tables.md#ext_user_type)).
12. **Retrieve peer users for worker or group checks when needed.** `GetUsers(userType, sourceSystemGroupID)` reloads users that share a type-and-group combination so validation can detect existing injured-worker logons ([SQL-019](../sql/SQL-019-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user), [EXT_USER_TYPE](../database-tables.md#ext_user_type)).
13. **Update the last-logon timestamp for sign-in activity.** `UpdateLastLogonDate` issues a direct update by AD user ID and returns `True` only when one row changed ([SQL-046](../sql/SQL-046-saif-business-external-users.md), [EXT_USER](../database-tables.md#ext_user)).

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
    Start([Registration, retrieval, or sign-in request]) --> Mode{New account?}
    Mode -->|Yes| Init[Initialize contact, user type, and status]
    Init --> Validate[Run uniqueness and field validation]
    Validate --> SaveRoot[Insert user row]
    SaveRoot --> SaveChildren[Save contact, questions, and types]
    Mode -->|No| Retrieve[Load user or forgotten-user match]
    Retrieve --> Edit{Update requested?}
    Edit -->|Yes| Update[Apply optimistic-concurrency update]
    Edit -->|No| Done([Return loaded account])
    Update --> Done
    SaveChildren --> Done
```

## Failure and edge-case behavior

- Email changes automatically restart the verification window on update.
- Test and production-test users receive different expiration windows during insert.
- Agency, policyholder, and medical-provider access-group combinations can fail child validation before the root user row is saved.
- A forgotten-user lookup can fail all three identity strategies and return `Nothing` without changing persisted data.
- Last-logon updates succeed only when the AD user ID matches exactly one row.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

## Sources
- `Business\SAIF.Business.External.Users\clsUser.vb`
- `Business\SAIF.Business.External.Users\clsPassword.vb`
- `Business\SAIF.Business.External.Users\mdlAuditor.vb`
- `Business\SAIF.Business.External.Users\mdlCodeMaps.vb`
- `Business\SAIF.Business.External.Users\mdlGroupAssociation.vb`
