Local mock environment
The CRM service depends on three external systems at runtime:
- Identity service — issues OAuth tokens and resolves
/api/user//api/policiesfor theVerifyOAuthToken+CheckPermissionmiddleware. - RabbitMQ —
IdentityEventConsumerconsumes 5 event types. - Postgres —
tenant_crmschema,unaccentextension.
For routine UI / store / page work you don't want to spin up identity
- RabbitMQ. This page documents the two support modes that let the SPA + backend run with stubs.
Mode A — Backend with a stubbed principal (recommended for SPA work)
The vast majority of frontend changes need a working /api/crm/*
endpoint that answers as if the request were authenticated. We
ship a .env.test-style flag that lets the backend bypass real
token validation and inject a fixed principal.
# tenant/tenant-backend-crm/.env
APP_ENV=local
CRM_FAKE_AUTH=true
CRM_FAKE_AUTH_USER_ID=01HFAKEUSER000000000000001
CRM_FAKE_AUTH_TENANT_ID=01HFAKETENANT00000000000001
CRM_FAKE_AUTH_IS_ADMIN=true
When CRM_FAKE_AUTH=true:
VerifyOAuthTokenshort-circuits and stuffsauth_userwith the fake id / tenant_id /is_admin=true.CheckPermissionshort-circuits on theis_adminflag (same path as production system-admin bypass).- No real identity call is made;
IdentityServiceis NOT used.
Never enable CRM_FAKE_AUTH in staging or production. The
middleware refuses to honor it when APP_ENV is not local. The
artisan command crm:check-fake-auth-guard (run in CI) asserts the
flag is false in any non-local config.
Mode B — Identity stub server
For end-to-end token-flow testing (login redirect, refresh token, policy update events) you DO need an identity-compatible endpoint. The bundled stub lives at:
tenant/tenant-backend-crm/dev/identity-stub.php
It's a 60-line PHP built-in-server script that serves:
| Route | Behavior |
|---|---|
GET /api/user | Returns the fake principal payload |
GET /api/policies | Returns one allow-all policy [{action: '*', effect: 'allow'}] |
GET /.well-known/jwks.json | Returns a static dev JWK pair |
POST /oauth/token | Returns a long-lived dev access_token |
Run it:
cd tenant/tenant-backend-crm
php -S 127.0.0.1:8000 dev/identity-stub.php
# point IDENTITY_URL=http://127.0.0.1:8000 in your .env
Note: this stub does not implement RabbitMQ event publishing, so
the CRM service won't see identity.policy.updated events.
CheckPermission will rely on the 1-hour policy cache; restart the
CRM service if you change policies in the stub.
Mode C — Skip the backend entirely (frontend MSW)
Not implemented yet. The SPA stores all go through axios; a future
mock-service-worker integration would let the frontend dev server
intercept /api/crm/* calls with fixtures. Tracking issue: see
F2 in the audit doc.
Generated SDK refresh
After any backend OpenAPI change (route, request body, response shape), regenerate the typed SDK:
# 1) Export the OpenAPI spec from the backend
cd tenant/tenant-backend-crm
php artisan scramble:export --path=../tenant-frontend/openapi/crm.json
# 2) Re-run the @hey-api generator
cd ../tenant-frontend
npm run api:sync
# 3) Commit the changes
git add src/api/generated/crm/
CI runs npm run api:check on every PR — it asserts there's no
uncommitted drift between the OpenAPI source and the generated
client. If it fails, run steps 1-3 locally and commit.
What .env keys are read by which mode
| Mode | Reads | Replaces |
|---|---|---|
| A (fake auth) | CRM_FAKE_AUTH, CRM_FAKE_AUTH_* | IDENTITY_URL, OAUTH_CLIENT_ID |
| B (identity stub) | IDENTITY_URL=http://127.0.0.1:8000, OAUTH_CLIENT_ID=* | — (real identity service) |
| C (MSW) | — | The entire backend (not yet wired) |