Skip to main content

iam-service — Local Dev Setup

Catalog · DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY

Get a working iam-service running locally in under 10 minutes. All external GCP dependencies (Cloud SQL, Memorystore, KMS, Pub/Sub) are emulated.

1. Prerequisites

ToolVersionNotes
Node.js20 LTSnvm recommended
pnpm9.xcorepack enable
Docker24+for compose stack
Docker Composev2bundled with modern Docker
GNU Makeanyoptional convenience
Git2.40+hooks via Husky
gcloud CLIlatestfor production-like flows; optional locally

Optional: mkcert for trusted local TLS certs, httpie or curl, jq.

2. Repository Layout (this service)

services/iam-service/
├── src/
│ ├── domain/ # pure TS aggregates, value objects, events
│ ├── application/ # use cases, ports, command/query handlers
│ ├── infrastructure/ # adapters: Postgres, Redis, KMS, Pub/Sub, IdP
│ ├── presentation/ # http (fastify), schemas, openapi
│ └── main.ts # composition root
├── test/
│ ├── unit/
│ ├── integration/
│ ├── contract/ # pact
│ └── e2e/ # playwright stack
├── db/
│ ├── migrations/ # node-pg-migrate
│ └── seeds/
├── openapi/
├── asyncapi/
├── docker-compose.dev.yml
├── docker-compose.e2e.yml
├── Dockerfile
├── package.json
├── tsconfig.json
└── README.md

3. Quick Start

git clone git@github.com:melmastoon/platform.git
cd platform/services/iam-service
cp .env.example .env.local
pnpm install
pnpm compose:up # spins up postgres, redis, fake-pubsub, fake-kms, mock-idp, mailhog
pnpm db:migrate
pnpm db:seed
pnpm dev # iam-service on http://localhost:4001

Check it's alive:

curl http://localhost:4001/health/ready
# → {"status":"ok","db":"ok","redis":"ok","kms":"ok"}

curl http://localhost:4001/.well-known/jwks.json | jq .

4. Compose Stack

docker-compose.dev.yml services:

ServicePortPurpose
postgres55432Postgres 15
redis56379Redis 7 (no AUTH locally)
fake-pubsub58085Pub/Sub emulator (gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators)
fake-kms58000local KMS shim (melmastoon/fake-kms:latest) — Ed25519 + HSM-style API
mock-idp58088Combined OIDC + SAML mock with seeded users
mailhog58025 (web 58025, smtp 51025)inbox for magic-link / reset emails

Tear down + reset:

pnpm compose:down -v # removes volumes

5. Environment Variables

.env.example (committed, redacted):

NODE_ENV=development
PORT=4001
LOG_LEVEL=debug

# Postgres
DATABASE_URL=postgres://iam:iam@localhost:55432/iam?sslmode=disable

# Redis
REDIS_URL=redis://localhost:56379/0

# Pub/Sub emulator
PUBSUB_EMULATOR_HOST=localhost:58085
PUBSUB_PROJECT_ID=melmastoon-dev

# Fake KMS
KMS_ENDPOINT=http://localhost:58000
KMS_KEY_RING=projects/melmastoon-dev/locations/me-central1/keyRings/iam
KMS_SIGNING_KEY=projects/melmastoon-dev/locations/me-central1/keyRings/iam/cryptoKeys/jwt-signing
KMS_TENANT_CA_KEY=projects/melmastoon-dev/locations/me-central1/keyRings/iam/cryptoKeys/tenant-ca

# JWT
JWT_ISSUER=https://auth.dev.melmastoon.local
JWT_AUDIENCE=https://api.dev.melmastoon.local
JWT_ACCESS_TTL_S=900
JWT_REFRESH_TTL_S=2592000
JWT_REFRESH_TTL_OFFLINE_S=604800

# Argon2id
PASSWORD_ARGON2_M=65536
PASSWORD_ARGON2_T=3
PASSWORD_ARGON2_P=1
PASSWORD_HASH_VERSION=1

# Sessions
IDLE_LOCK_S=300
ACCOUNT_LOCK_THRESHOLD=10
ACCOUNT_LOCK_WINDOW_S=900
ACCOUNT_LOCK_DURATION_S=900

# OIDC mock
DEV_OIDC_ISSUER=http://localhost:58088/oidc
DEV_OIDC_CLIENT_ID=iam-dev
DEV_OIDC_CLIENT_SECRET=dev-secret

# SAML mock
DEV_SAML_METADATA_URL=http://localhost:58088/saml/metadata

# Notification (mailhog)
SMTP_HOST=localhost
SMTP_PORT=51025
SMTP_FROM=auth-dev@melmastoon.local

# AI orchestrator (point at local stub or disable)
AI_ORCHESTRATOR_URL=http://localhost:4090
AI_ORCHESTRATOR_ENABLED=false

# HIBP
HIBP_API_KEY=disabled-locally
HIBP_FAIL_OPEN=true

# Tenant fingerprint pepper (KMS-backed in prod, file in dev)
TENANT_FINGERPRINT_SECRET=dev-pepper-do-not-use-in-prod

Never commit .env.local.

6. Seed Data

pnpm db:seed populates:

TenantTenant IDNotes
Dev Hotel Groupten_01HDEV0000000000000000000sample chain
Beach Resort Dohaten_01HDEV0000000000000000001property under chain
UserEmailPasswordRoleMFANotes
Platform Adminadmin@melmastoon.localDev_Admin!2026platform_adminTOTP enrolled (seed = JBSWY3DPEHPK3PXP)tenant-less
Tenant Ownerowner@dev.melmastoon.localDev_Owner!2026tenant_adminnonetenant …0000
Front Desk Stafffrontdesk@dev.melmastoon.localDev_Front!2026staffnonetenant …0001
Housekeepinghousekeep@dev.melmastoon.localDev_House!2026staffnonetenant …0001
Guestguest@example.comDev_Guest!2026guestnonetenant-less guest record

OIDC users in mock-idp (mock-idp/users.json): sso-user@dev.example, sso-admin@dev.example. Tenant-IdP linkage seeded so POST /api/v1/auth/sso/oidc/dev/init works for ten_…0001.

A seeded API key for the tenant-owner is printed during seed; copy it from console (mlk_...).

7. Common Commands

# Build
pnpm build

# Run
pnpm dev # tsx watch
pnpm start # built artifact

# Test
pnpm test # unit + integration (Testcontainers)
pnpm test:unit
pnpm test:integration
pnpm test:contract # pact provider verification (requires broker URL)
pnpm test:e2e # spins up docker-compose.e2e.yml + playwright

# DB
pnpm db:migrate
pnpm db:rollback
pnpm db:reset # drop + create + migrate + seed
pnpm db:psql # psql shell

# OpenAPI / AsyncAPI
pnpm openapi:generate
pnpm openapi:lint
pnpm asyncapi:lint

# Lint / format
pnpm lint
pnpm lint:fix
pnpm format

# Type check
pnpm typecheck

# Coverage
pnpm test:coverage

8. Useful Dev Scripts

8.1 Issue a test JWT for any seeded user

pnpm script:issue-jwt --email owner@dev.melmastoon.local
# → writes access_token + refresh_token to ./.dev-tokens.json

Use it:

TOKEN=$(jq -r .access_token .dev-tokens.json)
curl -H "Authorization: Bearer $TOKEN" http://localhost:4001/api/v1/users/me

8.2 Force-rotate the JWT signing key

pnpm script:rotate-jwt-key
# emits new kid, publishes JWKS, keeps old kid for 5 min

8.3 Simulate SSO callback

pnpm script:simulate-sso --tenant ten_01HDEV0000000000000000001 --email sso-user@dev.example

8.4 Decode a Melmastoon JWT

pnpm script:decode-jwt $TOKEN | jq .

8.5 List MailHog inbox

Browse http://localhost:58025 — magic links and password resets land here.

8.6 Trigger a fake credential-stuffing storm

pnpm script:storm --rps 50 --duration 30s
# observe http://localhost:4001/metrics

9. Connecting Electron Desktop Locally

cd ../../desktop/electron-shell
cp .env.example .env.local
# point AUTH_URL=http://localhost:4001
pnpm dev

The desktop app's first run requires:

  1. Login with frontdesk@dev.melmastoon.local.
  2. From the dev menu: "Bind device for offline" (re-prompts password = fresh-auth).
  3. After bind, you can disconnect network and the desktop will refresh tokens for up to 7 d via the device cert.

10. Troubleshooting

SymptomLikely causeFix
KMS unavailable on startupfake-kms not runningpnpm compose:up
Migration already executedstale DBpnpm db:reset
JWKS emptysigning key not initializedpnpm script:rotate-jwt-key
Pub/Sub: project not foundmissing PUBSUB_PROJECT_ID envcheck .env.local
OIDC discovery 404mock-idp not uppnpm compose:up
argon2 native build failedPython / build tools missinginstall build essentials per argon2 README
EADDRINUSE 4001another process on portlsof -i :4001 | awk 'NR>1{print $2}' | xargs kill
Magic-link not receivedSMTP env wrong, mailhog downcheck http://localhost:58025

11. IDE Setup

VS Code recommended extensions (.vscode/extensions.json):

  • dbaeumer.vscode-eslint
  • esbenp.prettier-vscode
  • vitest.explorer
  • cucumberopen.cucumber-official
  • mtxr.sqltools + mtxr.sqltools-driver-pg
  • humao.rest-client

Workspace settings enforce: format-on-save, organize imports, run ESLint on save.

12. Pre-commit Hooks

Husky runs:

  • pnpm lint:staged
  • pnpm typecheck (changed packages only)
  • pnpm test:unit --changed

commit-msg enforces conventional commits: feat(iam): …, fix(iam): ….

13. Nice-to-haves

  • gcloud auth application-default login if you want to test against a real GCP project (staging).
  • playwright install once before first E2E run.
  • pact broker docker is available via docker-compose.pact.yml.
  • signoz local stack via docker-compose.observability.yml for traces.