docs: finalize demo session persistence
This commit is contained in:
@@ -47,8 +47,8 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
- A `Merchant User` belongs to exactly one merchant in the MVP.
|
||||
- Demo usernames use stable lowercase ASCII identifiers such as `merchant-0001`. Comparison is case-insensitive, and rerunning the seed preserves an existing merchant's username.
|
||||
- Passwords are stored only as Argon2id encoded hashes with a unique salt and reviewed, versioned parameters. Plaintext passwords are accepted only at the login boundary or generated by the one-time credential workflow, then discarded.
|
||||
- An `Authenticated Session` belongs to one merchant user and records creation, last activity, idle expiry, absolute expiry, revocation, and minimal security provenance required by the approved retention policy.
|
||||
- The browser receives only a cryptographically random opaque session token. The server stores a non-reversible digest of that token, never the raw token.
|
||||
- An `Authenticated Session` is stored in PostgreSQL, belongs to one merchant user, and records creation, last activity, idle expiry, absolute expiry, and revocation.
|
||||
- The browser receives a 32-byte cryptographically random opaque session token. PostgreSQL stores only its SHA-256 digest, never the raw token. A fast digest is appropriate because the generated token has high entropy; password-style slow hashing adds cost without protecting a guessable secret.
|
||||
- Every merchant-scoped backend operation resolves merchant identity from the authenticated session. Client-supplied merchant identifiers may filter only within that boundary and never grant access.
|
||||
|
||||
## Authentication and session contract
|
||||
@@ -62,7 +62,9 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
7. Protected HTTP and SSE requests use the same authenticated merchant boundary. SSE replay or reconnect never survives session expiry or revocation.
|
||||
8. State-changing authenticated requests require a valid CSRF token and same-origin evidence. `SameSite=Lax` is defense in depth, not the only CSRF control; unexpected `Origin`/Fetch Metadata requests fail closed.
|
||||
9. Each user may have at most two active sessions. Creating a third session atomically revokes the oldest active session.
|
||||
10. Failed logins are throttled by both normalized username and source IP without permanently locking the account. A limited request returns `429` with `Retry-After`; exact thresholds remain configurable and require approval.
|
||||
10. Failed logins are throttled without permanent lockout: five failures per five minutes for a normalized username and 30 failures per five minutes for a source IP. A limited request returns `429` with `Retry-After`; thresholds remain validated configuration with these defaults.
|
||||
11. The production cookie is named `__Host-zpa_session`, has path `/`, and has no `Domain` attribute. This preserves host-only behavior required by the `__Host-` prefix.
|
||||
12. Session activity is persisted at most once every five minutes. This keeps the 12-hour idle deadline sufficiently accurate without turning every dashboard/API/SSE interaction into a PostgreSQL write.
|
||||
|
||||
## Demo credential workflow
|
||||
|
||||
@@ -72,6 +74,7 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
4. New plaintext credentials are displayed or exported once through an explicitly selected secure destination, with restrictive filesystem permissions where a file is requested.
|
||||
5. The workflow never writes plaintext credentials to application logs, import reports, source-controlled files, shell history by default, or database columns.
|
||||
6. Lost demo credentials are replaced only through a separate explicit operator action whose revocation and audit behavior must be specified before implementation.
|
||||
7. The replacement action sets a new random password, revokes both active sessions, and outputs the new credential once to an explicit operator-selected destination outside the repository.
|
||||
|
||||
## Threat model and abuse cases
|
||||
|
||||
@@ -97,7 +100,8 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
|
||||
- Record successful login, failed login category without username-existence disclosure, logout, session revocation, account disablement, and credential-generation operator actions.
|
||||
- Logs and metrics exclude passwords, password hashes, raw session tokens, cookie values, and sensitive payment data.
|
||||
- Security-event retention, IP/user-agent treatment, and privacy minimization remain open decisions.
|
||||
- Do not persist raw IP addresses or user-agent strings in application audit records for the demo. Rate-limit buckets may use keyed, non-reversible IP representations in disposable Redis entries that expire with the configured window.
|
||||
- Retain the minimal demo authentication audit records for 30 days, then delete them. This policy is replaced by ZarinPal's production SSO/audit requirements after acceptance.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
@@ -114,6 +118,10 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
- Login throttling applies independently to normalized-username and source-IP buckets, returns `429` plus `Retry-After`, and never permanently locks an account.
|
||||
- State-changing requests fail without both an approved CSRF token and valid same-origin evidence.
|
||||
- No user has more than two active sessions; the third successful login revokes the oldest session.
|
||||
- PostgreSQL contains only SHA-256 digests of 32-byte random session tokens; raw tokens never enter server storage or logs.
|
||||
- Session activity writes occur no more than once per five-minute window while idle expiry remains enforceable.
|
||||
- Production cookies use `__Host-zpa_session`, path `/`, no `Domain`, `Secure`, `HttpOnly`, and `SameSite=Lax`.
|
||||
- Minimal demo authentication audit records exclude raw IP/user-agent values and expire after 30 days.
|
||||
- Security tests cover enumeration resistance, session fixation/rotation, expiry, revocation, cross-tenant access, cookie flags, token-digest storage, and credential-output safety.
|
||||
|
||||
## Boundaries
|
||||
@@ -134,10 +142,10 @@ Allow hackathon judges and merchants to authenticate as distinct merchant users,
|
||||
- Require CSRF tokens plus strict same-origin evidence for state-changing authenticated requests.
|
||||
- Permit at most two active sessions per user; a third successful login revokes the oldest.
|
||||
- Treat local username/password authentication as a demo adapter that is replaced by ZarinPal SSO after acceptance; keep tenant authorization independent of the credential provider.
|
||||
- Store session rows in PostgreSQL, identify 32-byte random tokens by SHA-256 digest, and coalesce activity writes to at most once every five minutes.
|
||||
- Use the host-only `__Host-zpa_session` cookie name and retain minimal demo authentication audit records for 30 days without raw IP/user-agent values.
|
||||
- Default login throttling to five failures per normalized username and 30 per source IP in five minutes, without permanent lockout.
|
||||
|
||||
## Open decisions
|
||||
|
||||
- Exact login rate-limit thresholds and time windows
|
||||
- Exact session store, token-digest algorithm, cookie name/path, and activity-write throttling
|
||||
- Credential replacement and secure delivery procedure
|
||||
- Security-event IP/user-agent collection and retention
|
||||
No stakeholder-level Identity decisions remain. Implementation parameters may change only within the approved demo boundary and security invariants; ZarinPal SSO requirements require a new capability revision after acceptance.
|
||||
|
||||
@@ -36,7 +36,7 @@ Switch response codes remain nullable text on attempts until an authoritative de
|
||||
| `merchants` | Merchant profile and category membership | unique source merchant key; FK category |
|
||||
| `terminals` | Merchant-owned payment terminals | unique source terminal key; FK merchant |
|
||||
| `merchant_users` | Login identities | unique normalized username; FK merchant; password hash only |
|
||||
| `auth_sessions` | Revocable authenticated sessions | hashed opaque session token; FK user; expiry/revocation |
|
||||
| `auth_sessions` | Revocable demo authenticated sessions | unique SHA-256 digest of a 32-byte opaque token; FK user; last activity; idle/absolute expiry; revocation |
|
||||
|
||||
Synthetic profile fields carry an explicit `profile_is_synthetic` marker. Credentials are generated outside Git.
|
||||
|
||||
|
||||
@@ -9,18 +9,21 @@ The merchant dashboard needs simple username/password authentication for the hac
|
||||
|
||||
## Decision
|
||||
|
||||
Use cryptographically random opaque session tokens transported only in host-only `Secure`, `HttpOnly`, `SameSite=Lax` cookies. Store only a non-reversible token digest with server-side session state. Never place authentication tokens in local storage, session storage, URLs, or frontend-readable application state.
|
||||
Use 32-byte cryptographically random opaque session tokens transported only in a host-only `__Host-zpa_session` cookie with path `/`, no `Domain`, `Secure`, `HttpOnly`, and `SameSite=Lax`. Store sessions in PostgreSQL and persist only the SHA-256 token digest. Never place authentication tokens in local storage, session storage, URLs, frontend-readable application state, logs, or database columns.
|
||||
|
||||
Sessions expire after 12 hours of inactivity and no later than seven days after creation. Logout revokes the current session before clearing the cookie; disabling a merchant user revokes every active session for that user. Every protected HTTP request, SSE connection, and replay request resolves the merchant boundary from the current server-side session.
|
||||
|
||||
Persist activity at most once every five minutes. The idle timeout does not require per-request write precision, and coalescing avoids turning normal dashboard and SSE traffic into unnecessary PostgreSQL writes.
|
||||
|
||||
Passwords use Argon2id with unique salts and reviewed parameters. Expected login failures are externally generic. Demo credentials are generated through a one-time administrative workflow, displayed once, and never stored in Git or logs.
|
||||
|
||||
Demo usernames are stable lowercase `merchant-NNNN`-style identifiers with case-insensitive comparison. Failed logins are throttled by normalized username and source IP, return `429` with `Retry-After`, and never cause permanent account lockout. State-changing authenticated requests require CSRF tokens and strict same-origin evidence. A user may have at most two active sessions; a third successful login revokes the oldest.
|
||||
Demo usernames are stable lowercase `merchant-NNNN`-style identifiers with case-insensitive comparison. Failed logins default to five attempts per normalized username and 30 per source IP in five minutes, return `429` with `Retry-After`, and never cause permanent account lockout. State-changing authenticated requests require CSRF tokens and strict same-origin evidence. A user may have at most two active sessions; a third successful login revokes the oldest.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Logout, disablement, expiry, and incident response can invalidate access immediately.
|
||||
- The backend session store is on the protected-request path and must fail closed, be indexed, and have observable latency/availability.
|
||||
- A PostgreSQL outage also prevents authentication checks; this is preferable to trusting unverifiable client state, and no additional auth service is introduced for the demo.
|
||||
- SSE reconnect and replay must reauthorize instead of trusting a previous connection or event id.
|
||||
- Scaling API instances does not require sticky sessions when they share the approved authoritative session store.
|
||||
- Cookie scope, login-throttle thresholds, and the physical session-store contract remain explicit Identity-spec decisions rather than implicit framework defaults.
|
||||
|
||||
Reference in New Issue
Block a user