Appearance
Redis key registry
Status
Accepted for MVP documentation.
Date
2026-06-25
Context
This registry defines Redis key naming and payload boundaries for MyHoreca Pay MVP. Redis stores temporary/runtime state only. PostgreSQL remains the source of truth for permanent configuration, business records, and audit history.
Key naming convention
Canonical Redis key pattern:
text
mhpay:{env}:{tenantScope}:{domain}:{purpose}:{id}Fields:
| Field | Allowed values | Rule |
|---|---|---|
env | dev, test, staging, prod | Environment partition. |
tenantScope | org:{organizationId}, outlet:{outletId}, platform, user:{userId} | Use tenant ownership scope when it exists. Use user:{userId} only where scope is not tenant-owned. |
domain | auth, invite, syrve, mfa, throttle, job, context | Runtime domain. |
purpose | Concrete runtime purpose | Examples: object, active-by-org, runtime-token, challenge, counter. |
id | UUID, token hash, recipient hash, correlation id, connection id | Must not be a raw secret. |
Forbidden in Redis key names:
- plaintext invitation token;
- Redis password;
- Syrve/iiko password, API key, refresh secret, or connection secret;
- raw bank line;
- full account number;
- private customer payload.
TTL policy
| Runtime object | TTL |
|---|---|
| Invitations | 3 hours. |
| MFA/session challenges | Short TTL, target placeholder 5-15 minutes; exact value is a future implementation decision. |
| Auth/session temporary state | Session-bound TTL; exact duration is a future implementation decision. |
| Active context | Session-bound TTL. |
| Throttling counters | Window-based TTL matching the throttle window. |
| Background job cache | Bounded TTL; not a system of record. Exact duration is a future implementation decision. |
| Syrve/iiko runtime token/session | TTL less than or equal to upstream token/session expiry. |
Key registry
| Key | Key pattern | Value type | Owner/scope | TTL | Persistence expectation | Allowed payload | Forbidden payload | PostgreSQL audit counterpart | Cleanup/cancel behavior |
|---|---|---|---|---|---|---|---|---|---|
| Invite object | mhpay:{env}:org:{organizationId}:invite:object:{inviteId} | JSON object | Organization, optional Outlet in value | 3 hours | AOF/RDB may retain until TTL; still runtime-only | Invite id, storage organization id, organization id, optional location id, email hash, normalized email (runtime accept flow), role code, created-by user id, timestamps, isActive, correlation id, token hash | Plaintext invitation token, secrets, raw bank data | invite.created, invite.cancelled, invite.accepted | Expire by TTL; delete on cancel or accepted; fail closed if missing |
| Invite active index by organization | mhpay:{env}:org:{organizationId}:invite:active-by-org:{organizationId} | Set or sorted set of invite ids | Organization | Max 3 hours per member/object; cleanup may be opportunistic | Runtime index only | Invite ids, expiry timestamps if sorted set | Full invite payload, plaintext tokens, raw email | Same invite lifecycle audit events | Remove invite id on cancel/accepted; remove expired members during cleanup/list |
| Invite active index by recipient hash | mhpay:{env}:org:{organizationId}:invite:active-by-email-hash:{emailHash} | Set or string/list of invite ids | Organization plus recipient hash | Max 3 hours per member/object; cleanup may be opportunistic | Runtime lookup index only | Invite ids, hashed normalized lowercase email, expiry metadata | Raw email unless explicitly approved later, plaintext token, secrets | Same invite lifecycle audit events | Delete/update on cancel/accepted; clean expired invite ids |
| Runtime Syrve/iiko token/session | mhpay:{env}:org:{organizationId}:syrve:runtime-token:{connectionId} | JSON object or encrypted runtime blob | Organization connection | TTL <= upstream expiry | Runtime-only; may survive restart until TTL if persistence works | Runtime access token/session state, upstream expiry, connection id, correlation id | Permanent credentials, API keys, passwords, refresh secrets, connection secrets, token payload in audit | Optional token lifecycle metadata audit without token value | Delete on logout/connection disable/token invalidation; expire by TTL |
| Runtime Syrve/iiko token refresh lock | mhpay:{env}:org:{organizationId}:syrve:runtime-token-refresh-lock:{connectionId} | String lock owner id | Organization connection | Short lock TTL, currently 15 seconds | Runtime-only; safe against deadlock by TTL | Random lock owner/correlation metadata only | Passwords, password hashes, API keys, permanent secrets, runtime token/session value | Critical reconnect failure audit after retry exhaustion only | Delete by lock owner on release; expire by TTL |
| MFA challenge/session | mhpay:{env}:user:{userId}:mfa:challenge:{challengeId} | JSON object | User runtime scope | Short TTL, target placeholder 5-15 minutes | Runtime-only | Challenge id, user id, hashed challenge secret or verifier, created/expiry timestamps, attempt counter, correlation id | Plain challenge secret where hash/verifier is possible, passwords, recovery codes | MFA security audit when policy requires | Expire by TTL; delete on successful verification or cancellation |
| Auth/session temporary state | mhpay:{env}:user:{userId}:auth:session-state:{sessionStateId} | JSON object | User runtime scope | Session-bound TTL | Runtime-only | Session state id, nonce/hash, created/expiry timestamps, correlation id | Passwords, raw tokens if hash is possible, permanent user data | Login/session security audit when policy requires | Expire by TTL; delete after exchange/logout/invalidation |
| Active context | mhpay:{env}:user:{userId}:context:active:{sessionId} | JSON object | User runtime scope with selected organization/Outlet | Session-bound TTL | Runtime-only | User id, organization id, optional Outlet id, active role/scope hints, timestamp, correlation id | Permissions as permanent truth, secrets, raw bank data | None required; audit business actions separately | Expire with session; delete on logout/context reset |
| Throttling counter | mhpay:{env}:{tenantScope}:throttle:counter:{windowId} | Counter or hash | Platform, organization, user, or IP-derived scope as implementation requires | Window-based TTL | Runtime-only | Counter value, window start/end, scope id, action name | Raw credentials, raw IP if hashed/salted policy is chosen later, payload data | Security audit only for threshold events when policy requires | Expire by TTL; reset only by policy-authorized admin/system operation |
| Background job cache | mhpay:{env}:{tenantScope}:job:cache:{jobId} | JSON object/hash | Owning tenant scope or platform for system jobs | Bounded TTL; exact duration future implementation decision | Runtime cache only | Progress percent, last heartbeat, non-sensitive status, correlation id | Durable job source of truth, outbox payload, secrets, raw bank lines, full account numbers | Durable job/audit records outside Redis | Expire by TTL; delete after completion/cancellation; do not delete durable records |
Invitation JSON schema
Documentation schema for invite:object value:
json
{
"inviteId": "uuid-v7",
"storageOrganizationId": "uuid-v7",
"organizationId": "uuid-v7|null",
"locationId": "uuid-v7|null",
"normalizedEmail": "lowercase-email-for-runtime-accept",
"emailHash": "sha256-lowercase-email",
"tokenHash": "sha256-invite-token",
"roleCode": "OrganizationAdmin|Accountant|Viewer|...",
"createdByUserId": "uuid-v7",
"createdAtUtc": "ISO-8601",
"expiresAtUtc": "ISO-8601",
"correlationId": "string",
"isActive": true,
"expiresAtUnixSeconds": 1782483330
}Rules:
- Store token hash or a derived lookup key, not a plaintext invitation token.
- Do not include plaintext invitation token in JSON payload.
- Keep recipient email in normalized runtime form only when needed for accept flow; use
emailHashfor audit/index metadata. - Do not include raw bank lines, full account numbers, Syrve/iiko credentials, Redis passwords, or private payloads.
roleCodemust use canonical English role names.locationIdmay benullwhen the invite is organization-scoped; it maps to Outlet scope in product terminology.- Runtime invite indexes use sorted sets for
active-by-organdactive-by-email-hash, with invite id members formatted as UUID"N"and expiry timestamps as scores. - A RediSearch JSON index is initialized for invite object keys under the
mhpay:{env}:...namespace for lookup/list readiness.
Runtime Syrve/iiko token key
Canonical key pattern:
text
mhpay:{env}:org:{organizationId}:syrve:runtime-token:{connectionId}Rules:
- Store only short-lived runtime token/session state.
- TTL must be less than or equal to upstream token/session expiry.
- If upstream expiry is unknown, TTL is bounded by
Iiko.RestSessionVerification.SessionCacheSeconds; non-positive values skip Redis persistence. - Store payload as typed runtime DTO with
connectionId,organizationId,sessionId, created/expiry timestamps,sessionTransport, and optional safe correlation metadata. - Protect upstream login/refresh with
runtime-token-refresh-lock; lock key and value must not contain token/session/password/secret material. - On runtime
403/iiko_forbidden, delete the runtime token key, refresh under lock, and retry. Stop after 7 reconnect failures and write only safe critical failure audit metadata. - Permanent Syrve/iiko credentials, API keys, passwords, refresh secrets, and connection secrets are forbidden.
- Permanent credentials must remain in approved permanent secret storage or PostgreSQL-backed configuration.
- Runtime token values must not be written to PostgreSQL audit.
- Credentials rotation must delete this key after saving the new encrypted secret and before testing the connection.
Cleanup requirements
- Cancel invite deletes the invite object key and every active invite index entry.
- Accept invite deletes or marks inactive the invite object key and every active invite index entry.
- Syrve credentials rotation deletes the runtime token/session key for the rotated connection; PostgreSQL audit records only safe status metadata.
- Expired invite indexes must be cleaned opportunistically or by a bounded cleanup job.
- Redis cleanup must not delete PostgreSQL audit.
- Missing runtime objects must fail closed unless a future ADR explicitly defines recovery.
- Background cache cleanup must not remove durable job/outbox/business records.