Appearance
IAM And Profile Service Design
Decision
Use iam-service, not auth-service, as the Go platform boundary.
The legacy code has an AuthService class, but the target service owns more than login/logout. It is the identity and access-management boundary: accounts, credentials, sessions, refresh tokens, JWT/JWKS, roles, permissions, organization membership used for access control, invitations, login audit, and security events.
profile-service is separate because profile data is education-context data, not authentication state. It owns display/profile data for students, teachers, parents, and staff, plus public profile views. It references IAM accounts by account_id and does not handle password, token, session, role, or permission logic.
Boundaries
iam-service owns:
- account identity and credential state
- password verification and password-change/reset workflows
- access-token and refresh-token minting
- refresh-token hash/revoke/rotation state
- JWKS and gateway-compatible JWT claims
- roles, permissions, access-control memberships, and invites
- login/security audit events
iam-service must not own avatar, display profile, learning progress, exam result, course progress, wallet, subscription, payment, or usage quota state.
profile-service owns:
- public profile and display fields
- student, teacher, parent, and staff profile sections
- avatar/profile-contact metadata
- teacher KYC storage, self submit/read, owner-side admin review, and safe redaction
- parent-child relationship read models where profile UI needs them
- internal profile snapshots for service-to-service hydration
profile-service must not own passwords, refresh tokens, sessions, JWT signing, roles, permissions, or login/register flows.
Runtime And Data
Local development uses service-owned databases:
hoctapaz_iam_dbhoctapaz_profile_db
The shared local Postgres container creates these databases through deploy/postgres/init. Service code must use the service-owned database only; cross-service references use IDs and API/event contracts, not foreign keys.
Offline/local Kubernetes manifests for the original two-service slice live under deployments/k8s/local/. The active platform manifests live under deploy/k8s/services, and Helm renders the same services from deploy/helm/hoctapaz.
Flow Summary
Login/register/refresh:
- API Gateway receives
/api/auth/*. - During rehearsal, exact native carve-outs forward to
iam-service. iam-servicevalidates credentials or refresh-token state and returns the legacy-compatible session envelope.- Gateway/frontend keep using the existing access-token storage contract until public cutover is explicitly approved.
Profile update:
- Gateway authenticates the actor with IAM-compatible JWT claims.
- Profile routes forward to
profile-serviceonly after response-shape adapters are explicit. profile-servicepersists profile data byaccount_idwithout reading the IAM database.
Service verification:
- Services verify JWTs through gateway headers or IAM JWKS public keys when RS256 signing is configured.
- Services do not call IAM on every request unless a dynamic permission/session check is required.
- Tenant and ownership checks use owner-service APIs or synchronized read models, never cross-database joins.
Current Implementation Status
services/iam-serviceimplements the native auth/session foundation, password change/reset, Google provider-link/admin settings, persisted session records, login audit writes, RS256/JWKS signing when a private key is configured, directory/invite workflows, parent-student permission links, teacher subject scopes, staff permission scopes, migrations, Dockerfile, OpenAPI, docs, and tests.services/profile-serviceimplements profile snapshot/read/update foundations, role-profile tables, internal profile contacts, parent-child profile read models, teacher KYC self submit/read redaction, owner-side admin KYC review, public redaction without role/contact leakage, migrations, Dockerfile, OpenAPI, docs, and tests.- IAM/Profile usecase events use noop publishers by default:
iam.account.created,iam.account.updated,iam.account.disabled,iam.role.assigned,iam.organization.member_added,profile.created,profile.updated,profile.snapshot.upserted, andprofile.teacher_kyc.submitted/profile.teacher_kyc.reviewed. docs/microservices/legacy-user-auth-inventory.mdrecords the legacy source inventory and service split.- Default public routes remain conservative; native route tables are rehearsals until browser/runtime parity and rollback evidence are complete.
make test-iam-profile-route-rollbackvalidates that the auth/profile non-default route examples can roll back todeploy/gateway/routes.json, where broadauth,admin, andfallbackremainlegacy_proxy.make test-iam-profile-runtime-proofverifies the current static/render runtime wiring for IAM/Profile Compose, the two-service local K8s manifest, active offline K8s manifests, and Helm bootstrap.IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime make test-iam-profile-clean-composeis the opt-in live clean Compose smoke for IAM/Profile migration completion,schema_migrationsrows, Postgres health, service readiness, and IAM JWKS. It preflights host ports5433,8081, and8082by default; useIAM_PROFILE_HOST_POSTGRES_PORT,IAM_PROFILE_HOST_IAM_PORT, andIAM_PROFILE_HOST_PROFILE_PORTwhen a local developer stack already owns those ports. If the IAM/Profile service images are already built locally, setIAM_PROFILE_CLEAN_COMPOSE_USE_LOCAL_IMAGES=1andIAM_PROFILE_REBUILD_MIGRATOR_IMAGE=1to run the same proof with local images and Docker Compose--no-build.IAM_PROFILE_K8S_LIVE_CONFIRM=local-k8s make test-iam-profile-local-k8sis the opt-in local/offline Kubernetes smoke for the two-service IAM/Profile slice. It applies the local manifest into an isolated namespace, runs temporary IAM/Profile schema migration Jobs, checksschema_migrations, restarts deployments, verifies/healthz,/readyz, and IAM JWKS throughkubectl port-forward, then deletes the namespace by default. Build and loadhoctapaz/iam-service,hoctapaz/profile-service, andhoctapaz/migratorimages into the local cluster first. The smoke refuses non-local contexts by default;IAM_PROFILE_K8S_ALLOW_REMOTE=1is an explicit override for deliberate remote-cluster testing.
Remaining Risks
- Password change/reset and Google provider-link parity are implemented in native
iam-service, and exact/api/auth/*non-default gateway rehearsals exist. Static rollback rehearsal exists. Default promotion still needs browser/runtime proof, response-shape acceptance, production-like populated DB validation, and explicit approval before any route becomes default. - Teacher KYC self submit/read and owner-side admin review are native in profile-service, and exact self-service plus admin KYC gateway rehearsals are available in non-default route tables. Default promotion, admin-service compatibility wrapping, and browser/runtime proof still need dedicated cutover evidence.
- Current migrations keep
TEXTid columns to preserve legacy/backfill and string-id API compatibility, while service-owned PostgreSQL primary keys are promoted to UUID columns in the target IAM/Profile schemas. Production-like target validation, final text-id cleanup, and public route/browser promotion remain tracked in the IAM/Profile UUID cutover design. - IAM directory/audit endpoints remain gateway/internal surfaces. Direct management routes use service-local admin authz, while owner/internal routes require
X-Internal-Serviceplus the sharedX-Internal-Token; direct public exposure still needs gateway rehearsal, browser proof, and rollback evidence. - Profile keeps role/status as IAM-synchronized projection metadata only. These fields must not become profile-owned permission state.
- IAM/school organization-membership ownership must stay explicit to avoid split-brain tenant access decisions.
- Backfill and validation reports must run before any default
/api/auth/*cutover beyond already rehearsed routes. - Static/render runtime wiring is guarded, and clean Compose proof was refreshed on 2026-07-08 after the UUID dual-write migrations with local IAM/Profile images, a rebuilt local migrator image, alternate host ports
55433/18081/18082, and real IAM/Profile health, readiness, per-file migration-record, and JWKS checks for the current IAM 13-file and Profile 10-file migration sets. Local K8s proof was also refreshed on 2026-07-08 againstkind-hoctapaz-goafter loadinghoctapaz/iam-service:local,hoctapaz/profile-service:local, andhoctapaz/migrator:local; it verified migration Jobs, service-ownedschema_migrationscounts for the same 13/10 migration sets, service/healthz//readyz, and IAM JWKS, then removed the smoke namespace. Current default-route rollback evidence now exists throughmake test-iam-profile-route-rollback; production-like populated validation, browser proof for promoted routes, UUID primary-key promotion, and default route promotion remain pending.