# IAM Service (iam-service-net) — Service Documentation
> Auto-generated from code audit on 2026-03-13

## Overview
Identity and Access Management service for the GoodGo Platform. Handles user authentication (OAuth2/OIDC via Duende IdentityServer), user registration, role-based access control (RBAC), organizations, groups, access requests/reviews, privileged access management (PAM), identity verification (phone/email/document), audit logging, and compliance reporting.

- **Port**: 5001 (Development)
- **Database**: `iam_service` (PostgreSQL / Neon)
- **Base Route**: `api/v1`
- **Auth**: Duende IdentityServer (OAuth2 Password Grant, Authorization Code + PKCE, Client Credentials)
- **Framework**: .NET 10.0, C# 14

---

## API Endpoints

### AuthController — `api/v1/auth`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/auth/register` | None | Register a new user |
| POST | `/auth/change-password` | Bearer | Change user password |
| POST | `/auth/logout` | Bearer | Logout and revoke tokens |
| POST | `/auth/send-verification-email` | None | Send email verification link |
| POST | `/auth/confirm-email` | None | Confirm email with token |
| POST | `/auth/2fa/enable` | Bearer | Enable 2FA (returns QR code + recovery codes) |
| POST | `/auth/2fa/verify` | Bearer | Verify 2FA TOTP code to complete setup |
| POST | `/auth/2fa/disable` | Bearer | Disable 2FA (requires current code) |
| GET | `/auth/external-login/{provider}` | None | Initiate OAuth with Google/Facebook |
| GET | `/auth/external-callback` | None | Handle OAuth callback |
| GET | `/auth/linked-accounts` | Bearer | List linked external providers |

### OAuth2 Token Endpoint (Duende IdentityServer)
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/connect/token` | Client Credentials | OAuth2 token endpoint (password, authorization_code, client_credentials, refresh_token) |

### UsersController — `api/v1/users`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/users` | Bearer + RequireAdmin | Get all users (paginated) |
| GET | `/users/{id}` | Bearer + OwnerOrAdmin | Get user by ID |
| PUT | `/users/{id}` | Bearer + OwnerOrAdmin | Update user (firstName, lastName) |
| DELETE | `/users/{id}` | Bearer + RequireAdmin | Soft-delete (deactivate) user |
| GET | `/users/me` | Bearer | Get current user info from JWT claims |
| GET | `/users/{id}/roles` | Bearer | Get user's assigned roles |
| GET | `/users/{id}/permissions` | Bearer | Get user's permissions (from claims) |

### RolesController — `api/v1/roles`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/roles` | Bearer + RequireAdmin | Get all roles (paginated) |
| GET | `/roles/{id}` | Bearer + RequireAdmin | Get role by ID |
| POST | `/roles` | Bearer + RequireAdmin | Create a new role |
| PUT | `/roles/{id}` | Bearer + RequireAdmin | Update role (name, description) |
| DELETE | `/roles/{id}` | Bearer + RequireAdmin | Delete role (system roles protected) |
| POST | `/users/{userId}/roles` | Bearer + RequireAdmin | Assign role to user |
| DELETE | `/users/{userId}/roles/{roleName}` | Bearer + RequireAdmin | Remove role from user |

### GroupsController — `api/v1/groups`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/groups?organizationId=` | Bearer + RequireAdmin | Get groups by organization |
| GET | `/groups/{id}` | Bearer + RequireAdmin | Get group by ID |
| POST | `/groups` | Bearer + RequireAdmin | Create group |
| DELETE | `/groups/{id}` | Bearer + RequireAdmin | Soft-delete group |
| POST | `/groups/{id}/members` | Bearer + RequireAdmin | Add member to group |
| DELETE | `/groups/{id}/members/{userId}` | Bearer + RequireAdmin | Remove member from group |

### OrganizationsController — `api/v1/organizations`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/organizations/{id}` | Bearer + RequireAdmin | Get organization by ID |
| GET | `/organizations/slug/{slug}` | Bearer + RequireAdmin | Get organization by slug |
| POST | `/organizations` | Bearer + RequireAdmin | Create organization |
| PUT | `/organizations/{id}` | Bearer + RequireAdmin | Update organization |
| DELETE | `/organizations/{id}` | Bearer + RequireAdmin | Archive organization (soft delete) |
| GET | `/organizations/{id}/hierarchy` | Bearer + RequireAdmin | Get organization hierarchy |
| GET | `/organizations/{id}/children` | Bearer + RequireAdmin | Get child organizations |

### AccessRequestsController — `api/v1/access-requests`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/access-requests` | Bearer + RequireAdmin | Create access request |
| GET | `/access-requests/{id}` | Bearer + RequireAdmin | Get access request by ID |
| GET | `/access-requests?requesterId=` | Bearer + RequireAdmin | Get my access requests |
| GET | `/access-requests/pending?approverId=` | Bearer + RequireAdmin | Get pending approvals |
| POST | `/access-requests/{id}/submit` | Bearer + RequireAdmin | Submit request for approval |
| POST | `/access-requests/{id}/approve` | Bearer + RequireAdmin | Approve access request |
| POST | `/access-requests/{id}/reject` | Bearer + RequireAdmin | Reject access request |
| DELETE | `/access-requests/{id}` | Bearer + RequireAdmin | Cancel access request |

### AccessReviewsController — `api/v1/access-reviews`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/access-reviews` | Bearer + RequireAdmin | Create access review |
| GET | `/access-reviews/{id}` | Bearer + RequireAdmin | Get access review by ID |
| POST | `/access-reviews/{id}/items` | Bearer + RequireAdmin | Add item to review |
| POST | `/access-reviews/{id}/start` | Bearer + RequireAdmin | Start access review |
| POST | `/access-reviews/{id}/items/{itemId}/review` | Bearer + RequireAdmin | Certify or revoke an item |
| POST | `/access-reviews/{id}/complete` | Bearer + RequireAdmin | Complete access review |

### PrivilegedAccessController — `api/v1/privileged-access`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/privileged-access/request` | Bearer + RequireSuperAdmin | Request JIT privileged access |
| GET | `/privileged-access/active?userId=` | Bearer + RequireSuperAdmin | Get active privileged grants |
| POST | `/privileged-access/{id}/revoke` | Bearer + RequireSuperAdmin | Revoke privileged access |

### UserProfilesController — `api/v1/users`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/users/{userId}/profile` | Bearer | Get user profile |
| PUT | `/users/{userId}/profile` | Bearer | Update user profile (bio, timezone, locale, avatarUrl) |
| PUT | `/users/{userId}/profile/attributes/{key}` | Bearer | Set profile attribute (String/Number/Boolean/Date/Json) |

### VerificationsController — `api/v1/verifications`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/verifications/phone` | Bearer + RequireAdmin | Request phone OTP verification |
| POST | `/verifications/email` | Bearer + RequireAdmin | Request email OTP verification |
| POST | `/verifications/{id}/confirm` | Bearer + RequireAdmin | Confirm verification with code |

### AuditController — `api/v1/audit`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/audit/logs` | Bearer + RequireAuditor | Get audit logs (filtered by date, eventType, actor, resource) |

### ComplianceController — `api/v1/compliance`
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| POST | `/compliance/reports` | Bearer + RequireAuditor | Generate compliance report |
| GET | `/compliance/reports` | Bearer + RequireAuditor | Get compliance reports |
| GET | `/compliance/reports/{id}` | Bearer + RequireAuditor | Get report by ID |
| GET | `/compliance/violations` | Bearer + RequireAuditor | Get unresolved violations |
| POST | `/compliance/reports/{id}/complete` | Bearer + RequireAuditor | Complete compliance report |

### Health Endpoints
| Method | Route | Auth | Description |
|--------|-------|------|-------------|
| GET | `/health` | None | Full health check (PostgreSQL) |
| GET | `/health/live` | None | Liveness probe (app running) |
| GET | `/health/ready` | None | Readiness probe |

---

## Commands (Write Operations)

### Auth Commands

**RegisterUserCommand**
- Input: `(Email, Password, FirstName, LastName)`
- Output: `RegisterUserCommandResult(UserId, Email, FullName)`
- Logic: Check if user exists by email -> create ApplicationUser -> CreateAsync with password via UserManager -> raise UserRegisteredDomainEvent
- Validator: Email required + valid format; Password 8+ chars, uppercase, lowercase, digit, special; FirstName/LastName required, max 100

**ChangePasswordCommand**
- Input: `(UserId, CurrentPassword, NewPassword)`
- Output: `ChangePasswordCommandResult(Success, Message)`
- Validator: UserId required; CurrentPassword required; NewPassword 8+ chars with complexity rules; must differ from current

**LogoutCommand**
- Input: `(UserId)`
- Output: `LogoutCommandResult(Success, Message)`
- Logic: Signs out via SignInManager

**SendVerificationEmailCommand**
- Input: `(Email)`
- Output: `SendVerificationEmailResult`
- Validator: Email required, valid format, max 256

**ConfirmEmailCommand**
- Input: `(Email, Token)`
- Output: `ConfirmEmailResult(Success, Message)`
- Validator: Email required + valid; Token required, max 1024

**Enable2FACommand**
- Input: `(UserId)`
- Output: `Enable2FACommandResult(QrCodeBase64, ManualEntryKey, RecoveryCodes)`
- Validator: UserId required

**Verify2FACommand**
- Input: `(UserId, Code)`
- Output: `Verify2FAResult(Success, Message)`
- Validator: UserId required; Code required, exactly 6 digits

**Disable2FACommand**
- Input: `(UserId, Code)`
- Output: `Disable2FAResult(Success, Message)`
- Validator: UserId required; Code required, exactly 6 digits

**ExternalLoginCommand**
- Input: `(Provider, ProviderUserId, Email, Name?, PictureUrl?)`
- Output: `ExternalLoginResult(UserId, Email, IsNewUser, Success, Message)`
- Validator: Provider in [Google, Facebook, Apple]; ProviderUserId required max 256; Email required valid max 256; Name max 200; PictureUrl valid URI max 2048

### User Commands

**UpdateUserCommand**
- Input: `(UserId, FirstName?, LastName?)`
- Output: `UpdateUserCommandResult(UserId, Email, FirstName, LastName, FullName)`
- Validator: UserId required; FirstName/LastName max 100

**DeleteUserCommand**
- Input: `(UserId)`
- Output: `DeleteUserCommandResult(Success, Message)`
- Validator: UserId required

### Role Commands

**CreateRoleCommand**
- Input: `(Name, Description?)`
- Output: `CreateRoleCommandResult(Id, Name, Description, CreatedAt)`
- Validator: Name required, max 100, alphanumeric + _-. only; Description max 500

**UpdateRoleCommand**
- Input: `(RoleId, Name, Description?)`
- Output: `UpdateRoleCommandResult(Id, Name, Description)`
- Validator: RoleId required; Name required max 100 alphanumeric; Description max 500

**DeleteRoleCommand**
- Input: `(RoleId)`
- Validator: RoleId required. System roles cannot be deleted.

**AssignRoleToUserCommand**
- Input: `(UserId, RoleName)`
- Validator: UserId required; RoleName required max 100

**RemoveRoleFromUserCommand**
- Input: `(UserId, RoleName)`
- Validator: UserId required; RoleName required max 100

### Organization Commands

**CreateOrganizationCommand**
- Input: `(Name, Slug, Description?, ParentOrganizationId?)`
- Output: `CreateOrganizationResult(Id, Name, Slug, Description, ParentOrganizationId, Status, CreatedAt)`

**UpdateOrganizationCommand**
- Input: `(Id, Name, Description?)`
- Output: `UpdateOrganizationResult(Id, Name, Description, UpdatedAt)`

**ArchiveOrganizationCommand**
- Input: `(Id)`
- Logic: Soft-deletes by setting status to Archived. Cannot archive if has children.

### Group Commands

**CreateGroupCommand**
- Input: `(Name, OrganizationId, Description?)`
- Output: `CreateGroupResult(Id, Name, Description, OrganizationId, CreatedAt)`

**DeleteGroupCommand**
- Input: `(GroupId)`
- Logic: Soft-delete via IsDeleted flag

**AddGroupMemberCommand**
- Input: `(GroupId, UserId, RoleId?)`  — RoleId: 1=Member, 2=Admin, 3=Owner
- Output: `AddGroupMemberResult(GroupId, UserId, Role, JoinedAt)`

**RemoveGroupMemberCommand**
- Input: `(GroupId, UserId)`
- Logic: Cannot remove last owner

### Access Request Commands

**CreateAccessRequestCommand**
- Input: `(RequesterId, ResourceType, ResourceId, RequestedPermission, Justification?, Priority?, ApproverIds)`
- Priority: 1=Low, 2=Medium, 3=High, 4=Critical

**SubmitAccessRequestCommand** — Input: `(RequestId)`
**ApproveAccessRequestCommand** — Input: `(RequestId, ApproverId, Comments?)`
**RejectAccessRequestCommand** — Input: `(RequestId, ApproverId, Comments?)`
**CancelAccessRequestCommand** — Input: `(RequestId)`

### Access Review Commands

**CreateAccessReviewCommand** — Input: `(Name, Description?, OwnerId, Scope, DueDate)`
**AddAccessReviewItemCommand** — Input: `(ReviewId, UserId, ResourceType, ResourceId, Permission)`
**StartAccessReviewCommand** — Input: `(ReviewId)`
**ReviewItemCommand** — Input: `(ReviewId, ItemId, ReviewerUserId, Certify, Comments?)`
**CompleteAccessReviewCommand** — Input: `(ReviewId)`

### Privileged Access Commands

**RequestPrivilegedAccessCommand** — Input: `(UserId, RoleId, ResourceScope, Reason?, GrantedByUserId, DurationMinutes)` — Duration: 5-480 min
**RevokePrivilegedAccessCommand** — Input: `(GrantId, RevokedByUserId, Reason?)`

### Verification Commands

**RequestPhoneVerificationCommand** — Input: `(UserId, PhoneNumber)` — Generates 6-digit OTP, hashed with SHA256
**RequestEmailVerificationCommand** — Input: `(UserId, Email)` — Same OTP flow
**ConfirmVerificationCommand** — Input: `(VerificationId, Code)` — Max 5 attempts, 10-min expiry

### Compliance Commands

**GenerateComplianceReportCommand** — Input: `(Name, ReportTypeId, GeneratedByUserId)`
**CompleteComplianceReportCommand** — Input: `(ReportId, TotalChecks, PassedChecks, Summary?)`

---

## Queries (Read Operations)

**GetUsersQuery** — Input: `(PageNumber, PageSize)` — Returns paginated user list
**GetUserByIdQuery** — Input: `(UserId)` — Returns single user or null
**GetRolesQuery** — Input: `(PageNumber, PageSize)` — Returns paginated role list
**GetRoleByIdQuery** — Input: `(RoleId)` — Returns single role or null
**GetGroupByIdQuery** — Input: `(GroupId)` — Returns group with members/permissions
**GetGroupsByOrganizationQuery** — Input: `(OrganizationId)` — Returns list of groups
**GetOrganizationByIdQuery** — Input: `(OrgId)` — Returns organization or null
**GetOrganizationBySlugQuery** — Input: `(Slug)` — Returns organization by slug
**GetOrganizationHierarchyQuery** — Input: `(OrgId)` — Returns hierarchy chain
**GetChildOrganizationsQuery** — Input: `(OrgId)` — Returns direct children
**GetAccessRequestByIdQuery** — Input: `(RequestId)` — Returns access request with approvers
**GetMyAccessRequestsQuery** — Input: `(RequesterId)` — Returns requester's requests
**GetPendingApprovalsQuery** — Input: `(ApproverId)` — Returns requests pending for approver
**GetUserProfileQuery** — Input: `(UserId)` — Returns profile with attributes, address, phone
**GetAuditLogsQuery** — Input: `(FromDate?, ToDate?, EventTypeId?, ActorId?, ResourceType?, Skip, Take)` — Filtered audit logs
**GetComplianceReportsQuery** — Input: `(ReportTypeId?, Take)` — Returns compliance reports
**GetComplianceReportByIdQuery** — Input: `(ReportId)` — Returns report with violations
**GetUnresolvedViolationsQuery** — Returns all unresolved violations
**GetActivePrivilegedAccessQuery** — Input: `(UserId)` — Returns active PAM grants

---

## Domain Model

### ApplicationUser (extends IdentityUser\<Guid\>, IAggregateRoot)
- **Private Fields**: `_firstName`, `_lastName`, `_status` (UserStatus), `_createdAt`, `_lastLoginAt`
- **Public Getters**: FirstName, LastName, FullName, Status, StatusId, CreatedAt, LastLoginAt, DomainEvents
- **Behavior Methods**: `UpdateProfile(firstName, lastName)`, `RecordLogin()`, `Lock(until?)`, `Unlock()`, `Activate()`, `Disable()`
- **Domain Events**: `UserRegisteredDomainEvent`, `UserLoggedInDomainEvent`
- **Statuses**: Active(1), Locked(2), Disabled(3), PendingVerification(4)

### ApplicationRole (extends IdentityRole\<Guid\>, IAggregateRoot)
- **Private Fields**: `_description`, `_createdAt`, `_isSystemRole`
- **Behavior Methods**: `Update(name, description?)`
- **Domain Events**: `RoleAssignedDomainEvent`
- **System Roles (seeded)**: User, PremiumUser, Merchant, MerchantStaff, MerchantAdmin, Admin, SuperAdmin, Support

### Organization (Entity, IAggregateRoot)
- **Private Fields**: `_name`, `_slug`, `_description`, `_parentOrganizationId`, `_status`, `_settings`, `_createdAt`, `_updatedAt`
- **Behavior Methods**: `UpdateInfo(name, description)`, `UpdateSlug(slug)`, `SetParent(parentId?)`, `UpdateSettings(settings)`, `Activate()`, `Suspend()`, `Archive()`
- **Domain Events**: `OrganizationCreatedEvent(Id, Name, Slug)`, `OrganizationUpdatedEvent(Id, Name)`
- **Statuses**: Active(1), Suspended(2), PendingApproval(3), Archived(4)
- **Owned Entity**: OrganizationSettings (AllowUserRegistration, RequireEmailVerification, Require2FA, MaxUsersLimit, CustomDomain, SessionTimeoutMinutes)

### Group (Entity, IAggregateRoot)
- **Private Fields**: `_name`, `_description`, `_organizationId`, `_createdAt`, `_updatedAt`, `_isDeleted`
- **Collections**: `_members` (GroupMember), `_permissions` (GroupPermission)
- **Behavior Methods**: `Update(name, description)`, `AddMember(userId, role?, addedBy?)`, `RemoveMember(userId)`, `ChangeMemberRole(userId, newRole)`, `AddPermission(permission, resource?, grantedBy?)`, `RemovePermission(permission, resource?)`, `HasPermission(permission, resource?)`, `Delete()`, `Restore()`
- **Domain Events**: `GroupCreatedEvent`, `MemberAddedToGroupEvent`, `MemberRemovedFromGroupEvent`
- **Soft Delete**: Query filter on IsDeleted

### GroupMember (Entity)
- Fields: GroupId, UserId, Role (GroupRole), JoinedAt, AddedByUserId
- GroupRole: Member(1), Admin(2), Owner(3)

### GroupPermission (Entity)
- Fields: GroupId, Permission, Resource, GrantedAt, GrantedByUserId

### UserProfile (Entity)
- **Private Fields**: `_userId`, `_bio`, `_avatarUrl`, `_phoneNumber` (PhoneNumber VO), `_address` (Address VO), `_timezone`, `_locale`, `_dateOfBirth`, `_createdAt`, `_updatedAt`
- **Collections**: `_attributes` (ProfileAttribute)
- **Behavior Methods**: `UpdateBasicInfo(bio, timezone, locale)`, `SetAvatar(url)`, `SetPhoneNumber(phone)`, `SetAddress(address)`, `SetDateOfBirth(dob)`, `SetAttribute(key, value)` (String/Number/Boolean/Date/Json overloads), `RemoveAttribute(key)`, `GetAttributeValue(key)`, `GetAge()`
- **Value Objects**: PhoneNumber (CountryCode, NationalNumber), Address (Street, Street2, City, State, PostalCode, Country)

### ProfileAttribute (Entity)
- Fields: UserProfileId, Key, Value, ValueType (ProfileAttributeType)
- ProfileAttributeType: String(1), Number(2), Boolean(3), Date(4), Json(5)

### IdentityVerification (Entity, IAggregateRoot)
- **Private Fields**: `_userId`, `_type` (VerificationType), `_status` (VerificationStatus), `_verificationData`, `_verificationCodeHash`, `_requestedAt`, `_verifiedAt`, `_expiresAt`, `_attemptCount`, `_rejectionReason`, `_metadata`
- **Factory Methods**: `CreatePhoneVerification(userId, phoneNumber)` returns (Verification, OTP), `CreateEmailVerification(userId, email)` returns (Verification, OTP), `CreateDocumentVerification(userId, documentUrl, documentType?)`
- **Behavior Methods**: `VerifyCode(code)` (max 5 attempts), `MarkAsVerified()`, `MarkAsRejected(reason)`, `MarkAsExpired()`, `Cancel()`
- **Domain Events**: `VerificationRequestedEvent`, `VerificationCompletedEvent`
- **OTP**: 6-digit, SHA256 hashed, 10-min expiry
- **VerificationType**: Email(1), Phone(2), Document(3), Identity(4)
- **VerificationStatus**: Pending(1), InProgress(2), Verified(3), Rejected(4), Expired(5), Cancelled(6)

### AccessRequest (Entity, IAggregateRoot)
- **Private Fields**: `_requesterId`, `_resourceType`, `_resourceId`, `_requestedPermission`, `_justification`, `_status`, `_priority`, `_createdAt`, `_submittedAt`, `_resolvedAt`, `_expiresAt`
- **Collections**: `_approvers` (AccessRequestApprover)
- **Factory**: `Create(requesterId, resourceType, resourceId, permission, justification?, priority?)`
- **Behavior Methods**: `AddApprover(userId)`, `Submit(expirationDays=7)`, `Approve(approverId, comments?)`, `Reject(approverId, reason?)`, `Cancel()`, `Expire()`, `UpdateJustification(justification)`
- **Domain Events**: `AccessRequestCreatedEvent`, `AccessRequestSubmittedEvent`, `AccessRequestApprovedEvent`, `AccessRequestRejectedEvent`
- **Status Flow**: Draft -> Pending -> Approved/Rejected/Expired/Cancelled
- **Priority**: Low(1), Medium(2), High(3), Critical(4)

### AccessReview (Entity, IAggregateRoot)
- **Private Fields**: `_name`, `_description`, `_ownerId`, `_scope`, `_status`, `_createdAt`, `_startedAt`, `_dueDate`, `_completedAt`
- **Collections**: `_items` (AccessReviewItem)
- **Behavior Methods**: `AddItem(userId, resourceType, resourceId, permission)`, `Start()`, `ReviewItem(itemId, reviewerUserId, certify, comments?)`, `Complete()`, `Cancel()`
- **Domain Events**: `AccessReviewCreatedEvent`, `AccessReviewStartedEvent`, `AccessReviewCompletedEvent`
- **Status Flow**: Draft -> Active -> Completed/Cancelled
- **ReviewDecision**: Pending, Certify, Revoke

### PrivilegedAccessGrant (Entity, IAggregateRoot)
- **Private Fields**: `_userId`, `_roleId`, `_resourceScope`, `_reason`, `_grantedByUserId`, `_status`, `_createdAt`, `_startsAt`, `_expiresAt`, `_revokedAt`, `_revokedByUserId`, `_revocationReason`
- **Factory**: `Create(userId, roleId, resourceScope, reason?, grantedByUserId, durationMinutes=60)` (5-480 min), `CreateScheduled(...)` for future activation
- **Behavior Methods**: `Activate()`, `Revoke(revokedByUserId, reason?)`, `Expire()`, `Extend(additionalMinutes, extendedByUserId)` (5-240 min)
- **Domain Events**: `PrivilegedAccessGrantedEvent`, `PrivilegedAccessRevokedEvent`
- **Status**: Pending, Active, Expired, Revoked

### AuditLog (Entity, IAggregateRoot)
- **Fields**: EventType, ActorId, ActorEmail, ResourceType, ResourceId, Action, Details, IpAddress, UserAgent, Success, Timestamp
- **Factory**: `Create(eventType, resourceType, ...)`, `LoginEvent(...)`, `AccessGrantedEvent(...)`
- **AuditEventType**: Login(1), LoginFailed(2), Logout(3), PasswordChanged(4), PasswordReset(5), UserCreated(6), UserUpdated(7), UserDeleted(8), RoleAssigned(9), RoleRemoved(10), AccessGranted(11), AccessRevoked(12), PolicyViolation(13), MfaEnabled(14), MfaDisabled(15)

### ComplianceReport (Entity, IAggregateRoot)
- **Fields**: Name, ReportType, Status, GeneratedByUserId, CreatedAt, CompletedAt, Summary, TotalChecks, PassedChecks, FailedChecks
- **Collections**: `_violations` (ComplianceViolation)
- **Behavior Methods**: `StartGenerating()`, `SetResults(totalChecks, passedChecks, summary?)`, `AddViolation(rule, severity, description, remediation?)`, `Complete()`, `Fail(reason?)`
- **Domain Events**: `ComplianceReportCreatedEvent`, `ComplianceReportCompletedEvent`
- **ComplianceReportType**: GDPR, SOC2, HIPAA, ISO27001, PCI_DSS, Custom
- **ComplianceReportStatus**: Pending, Generating, Completed, Failed
- **ViolationSeverity**: Low, Medium, High, Critical

---

## Database Schema

### Identity Tables (ASP.NET Core Identity, custom table names)

| Table | Columns | Notes |
|-------|---------|-------|
| `users` | id (Guid PK), email, normalized_email, username, normalized_username, password_hash, security_stamp, concurrency_stamp, phone_number, phone_number_confirmed, two_factor_enabled, lockout_end, lockout_enabled, access_failed_count, email_confirmed, **first_name** (max 100), **last_name** (max 100), **created_at**, **last_login_at**, **status_id** (FK) | Custom fields via private field mapping |
| `roles` | id (Guid PK), name, normalized_name, concurrency_stamp, **description**, **created_at**, **is_system_role** | Custom fields added |
| `user_roles` | user_id, role_id | Join table |
| `user_claims` | id, user_id, claim_type, claim_value | User claims |
| `user_logins` | login_provider, provider_key, provider_display_name, user_id | External logins |
| `user_tokens` | user_id, login_provider, name, value | User tokens |
| `role_claims` | id, role_id, claim_type, claim_value | Role claims |
| `user_statuses` | id (int PK), name (max 50) | Seeded: Active(1), Locked(2), Disabled(3), PendingVerification(4) |

### Organizations

| Table | Columns | Indexes |
|-------|---------|---------|
| `organizations` | id (Guid PK), name (max 200), slug (max 100), description (max 1000), parent_organization_id (FK self), status_id (int), settings_allow_user_registration, settings_require_email_verification, settings_require_2fa, settings_max_users_limit, settings_custom_domain (max 255), settings_session_timeout_minutes, created_at, updated_at | UNIQUE ix_organizations_slug; FK fk_organizations_parent (Restrict delete) |
| `organization_statuses` | id (int PK), name (max 50) | Seeded: Active(1), Suspended(2), PendingApproval(3), Archived(4) |

### Groups

| Table | Columns | Indexes |
|-------|---------|---------|
| `groups` | id (Guid PK), name (max 200), description (max 1000), organization_id (Guid), is_deleted (bool, default false), created_at, updated_at | ix_groups_organization_id; Query filter: is_deleted = false |
| `group_members` | id (Guid PK), group_id (Guid FK), user_id (Guid), role_id (int), joined_at, added_by_user_id (Guid?) | UNIQUE ix_group_members_group_user; ix_group_members_user_id |
| `group_permissions` | id (Guid PK), group_id (Guid FK), permission (max 100), resource (max 500), granted_at, granted_by_user_id (Guid?) | UNIQUE ix_group_permissions_unique (group_id, permission, resource) |
| `group_roles` | id (int PK), name (max 50) | Seeded: Member(1), Admin(2), Owner(3) |

### User Profiles

| Table | Columns | Indexes |
|-------|---------|---------|
| `user_profiles` | id (Guid PK), user_id (Guid), bio (max 2000), avatar_url (max 500), timezone (max 50), locale (max 10), date_of_birth, phone_country_code (max 5), phone_national_number (max 15), address_street (max 200), address_street2 (max 200), address_city (max 100), address_state (max 100), address_postal_code (max 20), address_country (max 2, ISO 3166-1), created_at, updated_at | UNIQUE ix_user_profiles_user_id |
| `profile_attributes` | id (Guid PK), user_profile_id (Guid FK Cascade), key (max 100), value (max 4000), value_type_id (int), created_at, updated_at | UNIQUE ix_profile_attributes_profile_key (user_profile_id, key) |
| `profile_attribute_types` | id (int PK), name (max 50) | Seeded: String(1), Number(2), Boolean(3), Date(4), Json(5) |

### Identity Verification

| Table | Columns | Indexes |
|-------|---------|---------|
| `identity_verifications` | id (Guid PK), user_id (Guid), type_id (int), status_id (int), verification_data (max 1000), verification_code_hash (max 100), requested_at, verified_at, expires_at, attempt_count (default 0), rejection_reason (max 500), metadata (jsonb) | ix_identity_verifications_user_id; ix_identity_verifications_user_type_status |
| `verification_types` | id (int PK), name (max 50) | Seeded: Email(1), Phone(2), Document(3), Identity(4) |
| `verification_statuses` | id (int PK), name (max 50) | Seeded: Pending(1), InProgress(2), Verified(3), Rejected(4), Expired(5), Cancelled(6) |

### Access Requests

| Table | Columns | Indexes |
|-------|---------|---------|
| `AccessRequests` | Id (Guid PK), RequesterId (Guid), ResourceType (max 100), ResourceId (Guid), RequestedPermission (max 100), Justification (max 2000), StatusId (int, value conversion), PriorityId (int, value conversion), CreatedAt, SubmittedAt, ResolvedAt, ExpiresAt | IX_AccessRequests_RequesterId; IX_AccessRequests_Resource (ResourceType, ResourceId) |
| `AccessRequestApprovers` | Id (Guid PK), RequestId (Guid FK Cascade), UserId (Guid), ApprovalOrder (int), StatusId (int, value conversion), RespondedAt, Comments (max 1000) | IX_AccessRequestApprovers_UserId |

### Access Reviews

| Table | Columns | Indexes |
|-------|---------|---------|
| `AccessReviews` | Id (Guid PK), Name (max 200), Description (max 1000), OwnerId (Guid), Scope (max 200), StatusId (int), CreatedAt, StartedAt, DueDate, CompletedAt | IX_AccessReviews_OwnerId |
| `AccessReviewItems` | Id (Guid PK), ReviewId (Guid FK Cascade), UserId (Guid), ResourceType (max 100), ResourceId (Guid), Permission (max 100), DecisionId (int), ReviewedByUserId (Guid?), ReviewedAt, Comments (max 500) | IX_AccessReviewItems_UserId |

### Privileged Access

| Table | Columns | Indexes |
|-------|---------|---------|
| `PrivilegedAccessGrants` | Id (Guid PK), UserId (Guid), RoleId (Guid), ResourceScope (max 200), Reason (max 500), GrantedByUserId (Guid), StatusId (int), CreatedAt, StartsAt, ExpiresAt, RevokedAt, RevokedByUserId (Guid?), RevocationReason (max 500) | IX_PrivilegedAccessGrants_UserId; IX_PrivilegedAccessGrants_Active (UserId, RoleId, Status) |

### Audit & Compliance

| Table | Columns | Indexes |
|-------|---------|---------|
| `AuditLogs` | Id (Guid PK), EventTypeId (int), ActorId (Guid?), ActorEmail (max 256), ResourceType (max 100), ResourceId (Guid?), Action (max 200), Details (max 4000), IpAddress (max 45), UserAgent (max 500), Success (bool), Timestamp | IX_AuditLogs_Timestamp; IX_AuditLogs_ActorId; IX_AuditLogs_EventType; IX_AuditLogs_Resource |
| `ComplianceReports` | Id (Guid PK), Name (max 200), ReportTypeId (int), StatusId (int), GeneratedByUserId (Guid), CreatedAt, CompletedAt, Summary (max 4000), TotalChecks, PassedChecks, FailedChecks | IX_ComplianceReports_CreatedAt |
| `ComplianceViolations` | Id (Guid PK), ReportId (Guid FK Cascade), Rule (max 200), SeverityId (int), Description (max 1000), Remediation (max 1000), AffectedResource (max 200), Resolved (bool), ResolvedAt | — |

---

## Dependencies

### External Services Called
- **Redis** (167.114.174.113:6379) — Session/token caching via `ICacheService` / `RedisCacheService`
- **SMTP** (smtp.mailgun.org:587) — Email verification via `IEmailService` / `SmtpEmailService`
- **Google OAuth** — External login (ClientId/Secret from config, currently empty)
- **Facebook OAuth** — External login (AppId/Secret from config, currently empty)

### Infrastructure Services
- **ITwoFactorService** / `TotpTwoFactorService` — TOTP-based 2FA with QR code generation
- **ISocialLoginService** / `SocialLoginService` — Social login orchestration
- **IVerificationOtpDispatcher** — Dispatches OTP codes (registered in Program.cs)
- **ICacheService** / `RedisCacheService` — Redis-backed caching

### Domain Events Raised (potential cross-service integration points)
- `UserRegisteredDomainEvent` — User registered
- `UserLoggedInDomainEvent` — User logged in
- `RoleAssignedDomainEvent` — Role assigned to user
- `OrganizationCreatedEvent` / `OrganizationUpdatedEvent`
- `GroupCreatedEvent` / `MemberAddedToGroupEvent` / `MemberRemovedFromGroupEvent`
- `VerificationRequestedEvent` / `VerificationCompletedEvent`
- `AccessRequestCreatedEvent` / `AccessRequestSubmittedEvent` / `AccessRequestApprovedEvent` / `AccessRequestRejectedEvent`
- `AccessReviewCreatedEvent` / `AccessReviewStartedEvent` / `AccessReviewCompletedEvent`
- `PrivilegedAccessGrantedEvent` / `PrivilegedAccessRevokedEvent`
- `ComplianceReportCreatedEvent` / `ComplianceReportCompletedEvent`

### Repositories (10 total)
| Interface | Implementation | Aggregate |
|-----------|---------------|-----------|
| `IUserRepository` | `UserRepository` | ApplicationUser |
| `IRoleRepository` | `RoleRepository` | ApplicationRole |
| `IOrganizationRepository` | `OrganizationRepository` | Organization |
| `IGroupRepository` | `GroupRepository` | Group |
| `IIdentityVerificationRepository` | `IdentityVerificationRepository` | IdentityVerification |
| `IAccessRequestRepository` | `AccessRequestRepository` | AccessRequest |
| `IAccessReviewRepository` | `AccessReviewRepository` | AccessReview |
| `IPrivilegedAccessRepository` | `PrivilegedAccessRepository` | PrivilegedAccessGrant |
| `IAuditLogRepository` | `AuditLogRepository` | AuditLog |
| `IComplianceReportRepository` | `ComplianceReportRepository` | ComplianceReport |

---

## IdentityServer Configuration

### OAuth2 Clients
| Client ID | Grant Type | Token Lifetime | Notes |
|-----------|-----------|---------------|-------|
| `web-app` | Authorization Code + PKCE | 15 min (access), 7 days (refresh sliding) | Requires client secret |
| `mobile-app` | Authorization Code + PKCE | 15 min (access), 7 days (refresh sliding) | Public client, no secret |
| `service-client` | Client Credentials | Default | Service-to-service |
| `password-client` | Resource Owner Password | **8 hours** (access), 7 days (refresh) | Legacy/admin sessions |
| `swagger-ui` | Resource Owner Password | 1 hour (access), 1 day (refresh) | Testing convenience |

### Scopes
- `openid`, `profile`, `email`, `roles` (identity resources)
- `api` (API scope with role, email, name claims)

### API Resources
- `iam-api`, `goodgo-api`, `goodgo-services` — all use `api` scope with role/email/name claims

---

## MediatR Pipeline Behaviors

1. **LoggingBehavior** — Request/response logging with execution time
2. **ValidatorBehavior** — FluentValidation in pipeline (throws ValidationException)
3. **TransactionBehavior** — Auto-wraps Commands in DB transactions (skips Queries)

---

## Authorization Policies

| Policy | Requirement |
|--------|------------|
| `RequireAdmin` | User must have "Admin" or "SuperAdmin" role |
| `RequireSuperAdmin` | User must have "SuperAdmin" role |
| `RequireAuditor` | User must have "Admin", "SuperAdmin", or "Auditor" role |
| `OwnerOrAdmin` | User must be the resource owner or have Admin role |
| `LocalApi` | Bearer-authenticated user with "openid" scope |

---

## Error Handling (ProblemDetails RFC 7807)

| Exception | HTTP Status |
|-----------|-------------|
| `FluentValidation.ValidationException` | 400 Bad Request |
| `DuplicateResourceException` | 409 Conflict |
| `EntityNotFoundException` | 404 Not Found |
| `AuthenticationFailedException` | 401 Unauthorized |
| `BusinessRuleException` | 422 Unprocessable Entity |

---

## Configuration

### appsettings.json Key Settings
```
ConnectionStrings:DefaultConnection  — PostgreSQL (Neon cloud)
Redis:Host/Port/Password/Database    — Remote Redis instance
Jwt:Secret/Issuer/Audience           — JWT config (15 min access, 7 day refresh)
Email:SmtpServer/SmtpPort/SmtpLogin  — Mailgun SMTP (smtp.mailgun.org:587)
TwoFactor:Issuer/CodeLength/Validity — TOTP config (6 digits, 30s validity)
SocialLogin:Google/Facebook          — OAuth credentials (currently empty)
IdentityServer:Authority/IssuerUri   — http://localhost:5001 / http://iam-service
```

### Identity Password Policy
- Minimum 8 characters
- Require digit, lowercase, uppercase, non-alphanumeric
- Lockout: 15 min after 5 failed attempts

### Data Seeding (on startup)
System roles seeded via `DataSeeder.SeedRolesAsync()`:
User, PremiumUser, Merchant, MerchantStaff, MerchantAdmin, Admin, SuperAdmin, Support

### Migrations (7 total)
1. `20260112104902_InitialCreate` — Users, roles, Identity tables
2. `20260114074030_Phase2_IdentityManagement` — Organizations, groups, verifications
3. `20260114074751_AddProfileAttributes` — Profile attributes system
4. `20260114084200_Phase3A_AccessRequests` — Access request workflow
5. `20260114084754_Phase3A_AccessRequests_ValueConversion` — Status/Priority value converters
6. `20260114085640_Phase3B_AccessReviewsAndPAM` — Access reviews, privileged access
7. `20260114091114_Phase4A_AuditAndCompliance` — Audit logs, compliance reports

### Auto-Migration
EF Core migrations are auto-applied on startup in Development environment.
