Feature Branch: 001-iam-console
Created: 2025-12-18
Status: Draft
Input: Qortex IAM System Architecture Design
Qortex IAM is an independently deployable authentication and access management service providing unified capabilities for the Qortex product family (PIM, OIM, CIAM, Loyalty Console, Marketing Console):
- Administrator Authentication (AuthN)
- Access Control (AuthZ)
- Single Sign-On (SSO)
- Self-Sufficient: Complete functionality when deployed as single system
- Multi-System Sharing: Unified authentication, permissions, seamless SSO
- Minimal Intrusion: Business systems integrate via SDK + code generation
- Extensible: Support new system onboarding and identity source expansion
- Customer-Level Deployment: Each customer has isolated deployment and data
- Q: How are new users notified of their account creation? → A: System displays invitation link on page after user creation; admin manually shares link with invited user (no email sent by system)
- Q: Identity sources supported? → A: Built-in (Email/Password, Google OAuth) + External OIDC (customer IdP)
- Q: Permission model? → A: RBAC with system-level and feature-level permissions (format: {system}:{resource}:{action})
- Q: How is the first admin user created on initial system access? → A: System shows setup page to create first admin user; once created, setup page becomes inaccessible
- Q: How is Google OAuth configured? → A: Via UI in Identity Providers page (not environment variables); admin enters client ID and secret in the console
- Q: How are permissions managed? → A: Predefined by each system; admins cannot create/edit/delete permissions (read-only in IAM Console)
- Q: How do permissions enter IAM system? → A: Each system registers its permissions via API call on startup (POST /api/v1/systems/register)
- Q: How does IAM manage its own permissions? → A: IAM is treated as a system like PIM/OIM; it registers its own permissions (iam:user:, iam:role:, etc.) on startup; initial setup creates iam_admin role with all IAM permissions and assigns it to first user
- Q: What does "administrators" mean in the spec? → A: Permission-based; any user with the required IAM permission for that action (e.g., iam:user:create to create users) - not a fixed role
- Q: User name field structure for Japan market? → A: Replace single
namefield withgiven_name,family_name,given_name_kana,family_name_kana(Japanese market localization) - Q: Are kana fields required? → A: Only given_name and family_name are required; kana fields (given_name_kana, family_name_kana) are optional
- Q: User name display format? → A: Japanese format - family_name given_name (姓 名)
- Q: UI language? → A: English only (prototype phase)
- Q: Form field labels for name fields? → A: English labels (Given Name, Family Name, Given Name Kana, Family Name Kana)
- Q: Kana field input validation? → A: Validate kana fields accept only hiragana/katakana characters
- Q: Deployment topology for SSO? → A: Same parent domain with multiple subdomains, or multiple apps under the same domain differentiated by /sub-path-prefix/*
- Q: Should frontend handle JWT tokens? → A: No; browser login state is maintained via httpOnly cookies (session)
- Q: CSRF/SameSite preference? → A: Prefer SameSite=Lax
As an IAM administrator, I need to manage user accounts so that I can control who has access to Qortex systems and what they can do.
Why this priority: User management is the core function of IAM. Without users, no authentication or authorization can occur.
Independent Test: Can be fully tested by creating a user via invitation, viewing their details, updating their information, and deleting them.
Acceptance Scenarios:
- Given I am logged in as an IAM administrator, When I navigate to the Users page, Then I see a paginated list of all users with given name, family name (and kana variants), email, status, identity provider, and assigned roles
- Given I am on the Users page, When I click "Invite User" and enter email, given name, family name, given name kana, family name kana, and assign roles, Then the user is created with "invited" status and an invitation link is displayed for me to copy and share
- Given I am viewing a user, When I edit their roles or status, Then the changes are saved immediately
- Given I am viewing a user, When I delete the user and confirm, Then the user is removed from the system
- Given a user has been invited, When they click the invitation link, Then they are taken to a page to set their password
- Given a user sets their password via invitation, When they submit, Then their status changes to "active" and they can sign in
As an invited user, I need to set my password and sign in so that I can access the systems I'm authorized to use.
Why this priority: Email/Password is the primary built-in authentication method. Users cannot access any features without signing in.
Independent Test: Can be tested by completing invitation flow, signing in with credentials, and signing out.
Acceptance Scenarios:
- Given I received an invitation link from an administrator, When I click the link, Then I see a page to set my password
- Given I am setting my password, When I submit a valid password, Then my account is activated and an authenticated session is established
- Given I am on the sign-in page, When I enter valid email and password, Then I am authenticated and redirected to the appropriate system
- Given I am on the sign-in page, When I enter invalid credentials, Then I see an error message and remain on the sign-in page
- Given I am authenticated, When I sign out, Then my session ends and I am redirected to the sign-in page
As a user with Google account linked, I need to sign in via Google so that I can use my existing Google identity.
Why this priority: Google OAuth is a common enterprise authentication method, reducing password fatigue.
Independent Test: Can be tested by initiating Google login flow and verifying user is matched to internal account.
Acceptance Scenarios:
- Given I am on the sign-in page, When I click "Sign in with Google", Then I am redirected to Google authorization
- Given I authorize on Google, When I am redirected back, Then my Google email is matched to my internal user account
- Given my Google email matches an existing user, When the match succeeds, Then an authenticated session is established
- Given my Google email does not match any user, When the match fails, Then I see an error that I need to be invited first
As a user from a customer organization, I need to sign in via my company's identity provider so that I can use my existing corporate credentials.
Why this priority: Enterprise customers often require integration with their existing identity systems (Azure AD, Okta, etc.).
Independent Test: Can be tested by configuring an external OIDC provider and completing the login flow.
Acceptance Scenarios:
- Given an external OIDC provider is configured, When I visit the sign-in page, Then I see an option to sign in with the external provider
- Given I click the external provider option, When I am redirected, Then I authenticate with my corporate credentials
- Given I authenticate successfully, When I am redirected back, Then my email is matched to an internal user and an authenticated session is established
- Given my email does not match any user, When the match fails, Then I see an error that I need to be invited first
As an IAM administrator, I need to manage roles so that I can define permission sets for different job functions.
Why this priority: Roles are the foundation of RBAC. Without roles, permissions cannot be assigned to users.
Independent Test: Can be tested by creating a role, assigning permissions, and verifying users with that role have the expected access.
Acceptance Scenarios:
- Given I am on the Roles page, When I view the list, Then I see all roles with their names and permission counts
- Given I am creating a new role, When I enter name and select permissions, Then the role is created
- Given I am editing a role, When I add or remove permissions, Then the changes apply to all users with that role
- Given I am viewing a role, When I delete it, Then the role is removed (users lose those permissions)
As a user with assigned roles, I need the system to enforce my permissions so that I can only access features I'm authorized to use.
Why this priority: Permission enforcement is the core value of IAM - ensuring users can only do what they're allowed to do.
Independent Test: Can be tested by assigning different roles and verifying access is granted/denied appropriately.
Acceptance Scenarios:
- Given I have a role with "pim:access" permission, When I try to access PIM, Then I am allowed
- Given I do NOT have "pim:access" permission, When I try to access PIM, Then I am denied with 403 error
- Given I have "pim:product:create" permission, When I try to create a product, Then the action succeeds
- Given I do NOT have "pim:product:create" permission, When I try to create a product, Then the action is denied
As an IAM administrator, I need to configure identity providers via the console UI so that users can authenticate via different methods.
Why this priority: Flexibility in authentication methods is required for enterprise customers. UI-based configuration allows non-technical admins to manage auth settings without server access.
Independent Test: Can be tested by configuring Google OAuth and an external OIDC provider through the UI.
Acceptance Scenarios:
- Given I am on the Identity Providers page, When I view the list, Then I see configured providers with their status (enabled/disabled)
- Given I click "Add Provider" and select Google OAuth, When I enter client ID and client secret in the form, Then the configuration is saved and Google login becomes available on sign-in page
- Given I click "Add Provider" and select External OIDC, When I enter discovery URL, client ID, client secret, and scopes, Then the provider is added and appears on sign-in page
- Given I am viewing a configured provider, When I click "Edit", Then I can update the configuration (client ID, secret, etc.)
- Given I toggle a provider's enabled status to disabled, When users visit sign-in page, Then that login option is hidden
- Given I delete a provider, When I confirm deletion, Then the provider is removed and users can no longer use it to sign in
As an IAM administrator, I need to view registered systems and their permissions so that I can understand what access rights are available for role assignment.
Why this priority: Multi-system support requires knowing which systems exist and their permission definitions. Permissions are read-only (defined by each system).
Independent Test: Can be tested by viewing systems and their permissions after a system registers via API.
Acceptance Scenarios:
- Given I am on the Systems page, When I view the list, Then I see all registered systems with their status (enabled/disabled)
- Given I am viewing a system, When I click on it, Then I see all permissions defined for that system (read-only)
- Given a Qortex system starts up, When it calls POST /api/v1/systems/register with its permissions, Then the system and permissions are stored in IAM
- Given I am viewing permissions, When I try to edit or delete them, Then I cannot (permissions are read-only, managed by source systems)
- Given a system re-registers with updated permissions, When IAM receives the registration, Then permissions are updated (new ones added, removed ones deleted)
As a system deployer, I need to create the first administrator account when the system is accessed for the first time so that the IAM system can be managed.
Why this priority: Without an initial admin user, no one can manage users, roles, or permissions. This is a prerequisite for all other functionality.
Independent Test: Can be tested by accessing a fresh system deployment and completing the setup flow.
Acceptance Scenarios:
- Given the system has no users, When anyone accesses the system, Then they are redirected to the initial setup page
- Given I am on the initial setup page, When I enter email, given name, family name, given name kana, family name kana, and password for the first admin, Then the user is created and assigned the built-in IAM administrator role (the system role that grants full IAM access)
- Given the first admin user has been created, When anyone tries to access the setup page, Then they are redirected to the sign-in page (setup page is inaccessible)
- Given the first admin user exists, When I sign in with those credentials, Then I have full access to manage users, roles, and permissions
- Given the first admin user exists, When I sign in, Then the authenticated session contains all IAM permission codes registered by IAM and includes the built-in IAM administrator role
- What happens when an administrator tries to delete their own account? → Prevent self-deletion
- What happens when the last IAM admin tries to remove their iam:access permission? → Prevent orphaning
- How does the system handle duplicate emails during user creation? → Reject with validation error
- What happens when a user's session expires? → Redirect to login (and preserve intended destination when possible)
- What happens when an external OIDC provider is unavailable? → Show error, allow fallback to other methods
- What happens when a role is deleted that users are assigned to? → Users lose those permissions immediately
- What happens when filtering returns no results? → Show empty state message
These snapshots define the expected accessibility structure for each page. Use these as the basis for E2E tests with toMatchAriaSnapshot.
Route: /setup
User Story: US-9
- heading "Initial Setup" [level=1]
- textbox "Email"
- textbox "Given Name"
- textbox "Family Name"
- textbox "Given Name Kana"
- textbox "Family Name Kana"
- textbox "Password"
- textbox "Confirm Password"
- button "Create Administrator"
Route: /sign-in
User Story: US-2, US-3, US-4
- heading "Sign In" [level=1]
- textbox "Email"
- textbox "Password"
- button "Sign In"
- separator
- button "Sign in with Google"
- button /Sign in with .*/
Route: /invitation/:token
User Story: US-1, US-2
- heading "Set Your Password" [level=1]
- textbox "Password"
- textbox "Confirm Password"
- button "Activate Account"
Route: /users
User Story: US-1
- heading "Users" [level=1]
- textbox "Search users..."
- combobox "Status"
- combobox "Role"
- button "Invite User"
- table:
- rowgroup:
- row "Name Email Status Identity Provider Roles Actions"
- navigation "Pagination"
Route: /users (dialog)
User Story: US-1
- dialog "Invite User":
- heading "Invite User" [level=2]
- textbox "Email"
- textbox "Given Name"
- textbox "Family Name"
- textbox "Given Name Kana"
- textbox "Family Name Kana"
- group "Roles":
- checkbox /.*/
- button "Cancel"
- button "Send Invitation"
Route: /users (dialog after invite)
User Story: US-1
- dialog "Invitation Link":
- heading "Invitation Link" [level=2]
- textbox "Invitation URL" [readonly]
- button "Copy Link"
- button "Close"
Route: /users/:id
User Story: US-1
- heading /.*/ [level=1]
- text "Email"
- text "Status"
- text "Identity Provider"
- heading "Roles" [level=2]
- group "Assigned Roles":
- checkbox /.*/
- button "Save Changes"
- button "Regenerate Invitation Link"
- button "Delete User"
Route: /roles
User Story: US-5
- heading "Roles" [level=1]
- button "Create Role"
- table:
- rowgroup:
- row "Name Permissions Actions"
Route: /roles/new or /roles/:id
User Story: US-5
- heading /Create Role|Edit Role/ [level=1]
- textbox "Role Name"
- textbox "Description"
- heading "Permissions" [level=2]
- group /.* Permissions/:
- checkbox /.*/
- button "Cancel"
- button "Save Role"
- button "Delete Role"
Route: /identity-providers
User Story: US-7
- heading "Identity Providers" [level=1]
- button "Add Provider"
- table:
- rowgroup:
- row "Name Type Status Actions"
Route: /identity-providers/google (dialog)
User Story: US-7
- dialog "Configure Google OAuth":
- heading "Google OAuth" [level=2]
- textbox "Client ID"
- textbox "Client Secret"
- switch "Enabled"
- button "Cancel"
- button "Save"
Route: /identity-providers/oidc (dialog)
User Story: US-7
- dialog "Configure OIDC Provider":
- heading "External OIDC" [level=2]
- textbox "Provider Name"
- textbox "Discovery URL"
- textbox "Client ID"
- textbox "Client Secret"
- textbox "Scopes"
- switch "Enabled"
- button "Cancel"
- button "Save"
Route: /systems
User Story: US-8
- heading "Systems" [level=1]
- table:
- rowgroup:
- row "Name Code Status Permissions"
Route: /systems/:id
User Story: US-8
- heading /.*/ [level=1]
- text "System Code"
- text "Status"
- heading "Permissions" [level=2]
- table:
- rowgroup:
- row "Permission Code Name Type"
Initial System Setup
- FR-000a: System MUST detect when no users exist and redirect to initial setup page
- FR-000b: System MUST allow creation of first admin user with email, given_name, family_name, given_name_kana, family_name_kana, and password on setup page
- FR-000c: System MUST register IAM as a system with its own permissions on first startup (same as other Qortex systems)
- FR-000d: System MUST create a built-in IAM administrator role that contains all IAM permissions during initial setup
- FR-000e: System MUST assign the built-in IAM administrator role to the first user automatically
- FR-000f: System MUST make setup page inaccessible once first admin user exists (redirect to sign-in)
- FR-000g: System MUST NOT allow bypassing setup page check via direct URL access
- FR-000h: System MUST ensure the first admin's authenticated session exposes all IAM permission codes and includes the built-in IAM administrator role
User Management (requires respective iam:user: permissions)*
- FR-001: System MUST allow users with
iam:user:createpermission to create users with email, given_name, family_name, given_name_kana, family_name_kana, and assigned roles - FR-002: System MUST generate secure invitation token and display invitation link on page after user creation (no email sent by system)
- FR-002a: System MUST allow users with
iam:user:createpermission to copy invitation link to clipboard - FR-002b: System MUST allow users with
iam:user:updatepermission to regenerate invitation link for users with "invited" status - FR-003: System MUST allow invited users to set password via invitation link
- FR-004: System MUST change user status from "invited" to "active" after password is set
- FR-005: System MUST validate that email is unique across all users
- FR-006: System MUST allow users with
iam:user:readpermission to view a paginated list of all users - FR-007: System MUST allow users with
iam:user:updatepermission to update user roles and status - FR-008: System MUST allow users with
iam:user:deletepermission to delete users (except self-deletion) - FR-009: System MUST support user statuses: active, inactive, invited, suspended
- FR-010: System MUST display user's identity provider (local, google, oidc)
Authentication - Email/Password
- FR-011: System MUST provide email/password sign-in for local users
- FR-012: System MUST establish an authenticated session for a successfully authenticated user
- FR-012a: System MUST persist login state using httpOnly cookies (frontend does not manage tokens directly)
- FR-013: System MUST support SSO across Qortex systems when deployed under the same parent domain, including:
- Multiple subdomains under the same parent domain
- Multiple apps under the same domain differentiated by a /sub-path-prefix/*
- FR-014: System MUST provide sign-out mechanism that invalidates the server-side session and clears the session cookie
- FR-015: System MUST renew/extend an active session automatically on user activity, without requiring re-authentication
- FR-015a: System MUST enforce a session expiration policy with:
- Idle timeout: 2 hours
- Absolute lifetime: 7 days
- FR-015b: System MUST set authenticated session cookies with SameSite=Lax and MUST protect state-changing requests against CSRF attacks
Authentication - Programmatic Access (Non-Browser Clients)
- FR-021a: System MUST support non-browser clients (e.g., AI agents) accessing the same data APIs without relying on browser cookies
- FR-021b: System MUST provide an authentication mechanism for non-browser clients based on revocable API credentials (e.g., API tokens) sent via request headers
- FR-021c: System MUST allow administrators to revoke non-browser client credentials, and revocation MUST take effect immediately for subsequent requests
Authentication - Google OAuth
- FR-016: System MUST support Google OAuth login when configured
- FR-017: System MUST match Google email to existing user account
- FR-018: System MUST reject Google login if no matching user exists (invitation required)
Identity Provider Management (UI-based)
- FR-022: System MUST provide UI form to configure Google OAuth (client ID, client secret)
- FR-023: System MUST provide UI form to configure external OIDC providers (discovery URL, client ID, client secret, scopes)
- FR-024: System MUST allow enabling/disabling identity providers via UI toggle
- FR-025: System MUST show available login options on sign-in page based on enabled providers
- FR-025a: System MUST allow editing existing provider configurations via UI
- FR-025b: System MUST allow deleting identity providers (except built-in local provider)
- FR-025c: System MUST validate provider configuration before saving (e.g., test OIDC discovery URL)
Role & Permission Management
- FR-026: System MUST support RBAC with roles as permission collections
- FR-027: System MUST use permission format: {system}:{resource}:{action}
- FR-028: System MUST support system-level permissions (e.g., pim:access)
- FR-029: System MUST support feature-level permissions (e.g., pim:product:create)
- FR-030: System MUST allow creating, editing, and deleting custom roles
- FR-031: System MUST provide a way for clients to obtain the authenticated user's permissions for client-side checks
- FR-032: System MUST enforce permissions on API endpoints
System & Permission Registration
- FR-033: System MUST maintain registry of Qortex systems (IAM, PIM, OIM, etc.)
- FR-034: System MUST store permissions per system (read-only, defined by source systems)
- FR-035: System MUST provide API endpoint POST /api/v1/systems/register for system registration
- FR-035a: Registration payload MUST include system code, name, and array of permissions
- FR-035b: Each permission MUST include code (format: {system}:{resource}:{action}), name, and type (system/feature)
- FR-035c: System MUST update permissions on re-registration (add new, remove deleted)
- FR-035d: System MUST NOT allow manual creation/edit/deletion of permissions via UI (read-only display)
- FR-035e: System registration API MUST be authenticated with system API key
Search and Filtering
- FR-036: System MUST allow filtering users by status
- FR-037: System MUST allow filtering users by role
- FR-038: System MUST allow searching users by email, given_name, family_name, or kana fields
- FR-039: System MUST support cursor-based pagination
- User: Person with system access. Attributes: id, email, given_name (required), family_name (required), given_name_kana (optional), family_name_kana (optional), status, identity_provider_id, password_hash, invitation_token, created_at, updated_at
- Role: Named permission collection. Attributes: id, code, name, description, is_system (preset vs custom)
- Permission: Access right. Attributes: id, system_id, code, name, type (system/feature)
- System: Qortex product. Attributes: id, code, name, description, enabled
- IdentityProvider: Auth source. Attributes: id, type (local/google/oidc), name, config (jsonb), enabled, is_default
- UserRole: Many-to-many join. Attributes: user_id, role_id
- RolePermission: Many-to-many join. Attributes: role_id, permission_id
Format: {system}:{resource}:{action}
| Action | Description |
|---|---|
| access | System-level access permission |
| create | Create resource |
| read | View resource |
| update | Modify resource |
| delete | Remove resource |
| import | Import data |
| export | Export data |
| manage | Full control (includes all actions) |
Examples:
iam:access- Access IAM Consoleiam:user:create- Create userspim:product:read- View productsoim:order:refund- Process refunds
IAM registers itself as a system with the following permissions:
| Permission Code | Name | Type |
|---|---|---|
iam:access |
Access IAM Console | system |
iam:user:create |
Create Users | feature |
iam:user:read |
View Users | feature |
iam:user:update |
Update Users | feature |
iam:user:delete |
Delete Users | feature |
iam:role:create |
Create Roles | feature |
iam:role:read |
View Roles | feature |
iam:role:update |
Update Roles | feature |
iam:role:delete |
Delete Roles | feature |
iam:idp:create |
Create Identity Providers | feature |
iam:idp:read |
View Identity Providers | feature |
iam:idp:update |
Update Identity Providers | feature |
iam:idp:delete |
Delete Identity Providers | feature |
iam:system:read |
View Systems | feature |
iam_admin role (created during initial setup): Contains all IAM permissions above.
- SC-001: Administrators can invite a new user in under 1 minute
- SC-002: Invited users can set password and sign in within 2 minutes of clicking link
- SC-003: Users can sign in on first attempt with valid credentials
- SC-004: Google/OIDC login completes in under 5 seconds after provider auth
- SC-005: Session renewal happens transparently without user action
- SC-006: Permission checks complete in under 10ms
- SC-007: Role changes take effect immediately on next request
- SC-008: System displays appropriate error messages for all auth failures
- SC-009: SSO works seamlessly across all Qortex systems on same domain
- SC-010: 100% of unauthorized access attempts are blocked