Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries, Domain Model, Database Schema, Integration Events, Dependencies, Configuration. Generated by 23 parallel audit agents reading actual source code. Key corrections from audit: - inventory-service: 12 commands/6 queries (was listed as scaffold) - promotion-service: 12 commands/10 queries (was listed as 0) - mission-service: 4 commands/7 queries (was listed as 0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
35 KiB
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 registeredUserLoggedInDomainEvent— User logged inRoleAssignedDomainEvent— Role assigned to userOrganizationCreatedEvent/OrganizationUpdatedEventGroupCreatedEvent/MemberAddedToGroupEvent/MemberRemovedFromGroupEventVerificationRequestedEvent/VerificationCompletedEventAccessRequestCreatedEvent/AccessRequestSubmittedEvent/AccessRequestApprovedEvent/AccessRequestRejectedEventAccessReviewCreatedEvent/AccessReviewStartedEvent/AccessReviewCompletedEventPrivilegedAccessGrantedEvent/PrivilegedAccessRevokedEventComplianceReportCreatedEvent/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 useapiscope with role/email/name claims
MediatR Pipeline Behaviors
- LoggingBehavior — Request/response logging with execution time
- ValidatorBehavior — FluentValidation in pipeline (throws ValidationException)
- 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)
20260112104902_InitialCreate— Users, roles, Identity tables20260114074030_Phase2_IdentityManagement— Organizations, groups, verifications20260114074751_AddProfileAttributes— Profile attributes system20260114084200_Phase3A_AccessRequests— Access request workflow20260114084754_Phase3A_AccessRequests_ValueConversion— Status/Priority value converters20260114085640_Phase3B_AccessReviewsAndPAM— Access reviews, privileged access20260114091114_Phase4A_AuditAndCompliance— Audit logs, compliance reports
Auto-Migration
EF Core migrations are auto-applied on startup in Development environment.