Commit graph

18 commits

Author SHA1 Message Date
Jan Willem Mannaerts
bf5f71aa35 Remove vote on leave/disconnect, require 2+ members, restore vote on sync
All checks were successful
Build & Push Container Image / build (push) Successful in 10s
- Add removeVote (only during VOTING state, preserves revealed votes)
- Remove user's vote on disconnect, room:leave, and kick
- After vote removal, check auto-reveal for remaining members
- Send poker:my-vote on sync so card selection is restored
- Disable voting cards until at least 2 participants are present

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:21:45 +01:00
Jan Willem Mannaerts
0b713d3858 Remove manual reveal button and fix vote sync on session transitions
All checks were successful
Build & Push Container Image / build (push) Successful in 12s
Remove the "Reveal Votes" button since auto-reveal handles this. Add
poker:sync event so PokerRoom fetches current vote state on mount,
fixing checkmarks not appearing for other users on subsequent rounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:33:30 +01:00
Jan Willem Mannaerts
36c8c5f6f4 Filter stale non-member votes from reveal logic
All checks were successful
Build & Push Container Image / build (push) Successful in 5s
revealIfComplete and forceReveal now accept a Set of room member keys
and only count votes from current members. Stale votes from users who
left the room are stripped before computing averages. emitSessionState
also filters votedUserKeys and revealed votes to members only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:30:50 +01:00
Jan Willem Mannaerts
2d78b9ff07 Track participants at room level to fix premature auto-reveal
All checks were successful
Build & Push Container Image / build (push) Successful in 8s
Participants were tracked per-session, so each new issue started with 0
participants. The first user to join+vote saw 1/1 = all voted, triggering
premature reveal. Now members are tracked on the room object and persist
across issues. revealIfComplete compares votes against room member count.

Also fixes: disconnect handler was dead code (Socket.IO v4 empties
socket.rooms before firing disconnect) — replaced with disconnecting.

Added manual "Reveal Votes" button and poker:reveal socket handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:55:49 +01:00
Jan Willem Mannaerts
3051119405 Add poker:leave on unmount and poker:kick to remove ghost participants
All checks were successful
Build & Push Container Image / build (push) Successful in 9s
Frontend now emits poker:leave when PokerRoom unmounts, preventing
ghost participants. Also adds poker:kick socket event so any session
participant can remove a stale user — shows a small X button next to
each participant. Fixes deadlocked sessions where a disconnected user
blocks reveal (votes.size can never equal participants.size).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:54:26 +01:00
Jan Willem Mannaerts
a7aac985d2 Clean up session participants on socket disconnect
All checks were successful
Build & Push Container Image / build (push) Successful in 5s
When a user closes their browser or navigates away, the socket
disconnects without emitting poker:leave. The participant stayed
in the NATS session data indefinitely. Now the disconnect handler
iterates the socket's poker rooms and calls leaveSession for each,
then broadcasts the updated state to remaining participants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:42:51 +01:00
Jan Willem Mannaerts
5b83dd7ed0 Fix OAuth login failure for Atlassian accounts with colons in ID
All checks were successful
Build & Push Container Image / build (push) Successful in 6s
Some Atlassian account IDs use the format '70121:uuid' which contains
a colon — an invalid character in NATS KV keys (mapped to subjects).
Sanitize by replacing colons with underscores in the OAuth KV key.

No migration needed: affected users never had stored entries since
the PUT always failed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:31:35 +01:00
Jan Willem Mannaerts
f17072116d Fix 0-point estimate treated as unestimated causing poker loop
All checks were successful
Build & Push Container Image / build (push) Successful in 10s
Root cause: estimate field used || 0 which collapsed both null
(never estimated) and 0 (estimated as zero) into the same value.
The filter !estimate then matched both, so saving 0 points left
the issue in the unestimated list and advanceToNext re-pokered it.

Fix: use ?? null so 0 is a distinct valid estimate, and filter on
== null to only match truly unestimated issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:31:04 +01:00
Jan Willem Mannaerts
4f5c71e811 Fix infinite loop when saving 0-point estimate
All checks were successful
Build & Push Container Image / build (push) Successful in 10s
Saving an estimate of 0 caused advanceToNext to re-poker the same
issue repeatedly: !0 is true and 0 === 0 matches the "unestimated"
check. Track pokered issues with an `estimated` flag so they are
skipped regardless of estimate value.

Also guard against empty session index entries in createScopedSession
(NATS KV tombstones after delete can return empty values, producing
an invalid trailing-dot key that crashes Bucket.get).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:20:22 +01:00
Jan Willem Mannaerts
c31161af19 Add Prometheus metrics and Grafana dashboard
All checks were successful
Build & Push Container Image / build (push) Successful in 9s
Instrument backend with prom-client: HTTP request count/latency,
WebSocket connections, Jira API health, session/vote/room counters,
and unique user/tenant tracking. Expose unauthenticated /metrics
endpoint. Include pre-built Grafana dashboard JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 01:02:36 +01:00
Jan Willem Mannaerts
99cdd5b102 Fix CSP: allow wp.com image proxy for Gravatar redirects
All checks were successful
Build & Push Container Image / build (push) Successful in 6s
Gravatar 302 redirects to i0.wp.com for default/fallback avatars.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:56:37 +01:00
Jan Willem Mannaerts
4d8c2a301c Fix CSP to allow Google Fonts and Gravatar avatars
All checks were successful
Build & Push Container Image / build (push) Successful in 5s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:50:02 +01:00
Jan Willem Mannaerts
31dfbe3cca Broaden CSP img-src to allow all Atlassian avatar domains
All checks were successful
Build & Push Container Image / build (push) Successful in 5s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:40:02 +01:00
Jan Willem Mannaerts
03ba19042d Harden security across frontend and backend
All checks were successful
Build & Push Container Image / build (push) Successful in 11s
1. AdfRenderer: validate href starts with https?:// before rendering links
2. Logout route: add requireAuth middleware
3. Jira API params: validate sprintId, boardId, issueIdOrKey are alphanumeric
4. CSP header: add Content-Security-Policy with restrictive defaults
5. OAuth callback: align frontendUrl fallback with index.js
6. Rate limiting: express-rate-limit on API routes + Socket.IO event throttling
7. Session KV keys: prefix with cloudId for tenant isolation defense-in-depth
8. saveScopedEstimate: use withSessionCas for atomic read-update-delete

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:05:48 +01:00
Jan Willem Mannaerts
3ab584e2ab Update env example with full Jira scopes and add source code link to privacy page
All checks were successful
Build & Push Container Image / build (push) Successful in 8s
- Added all required Jira OAuth scopes to .env.example
- Added NATS_TOKEN and JIRA_MOCK_FALLBACK to .env.example
- Added open source section to privacy policy linking to the repo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:32:59 +01:00
Jan Willem Mannaerts
45dbd341a3 Fix Socket.IO origin check and force WebSocket-only transport
All checks were successful
Build & Push Container Image / build (push) Successful in 8s
Same-origin requests omit the Origin header, which was rejected in
production. Also restrict to WebSocket transport on both client and
server to eliminate need for sticky sessions with multiple replicas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:28:07 +01:00
Jan Willem Mannaerts
047d0de485 Remove dead normalizeIssue function and make legal pages linkable
All checks were successful
Build & Push Container Image / build (push) Successful in 8s
- Remove unused normalizeIssue and JIRA_STORY_POINTS_FIELD env var
- Add URL routing for /terms, /privacy, /support pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:42:01 +01:00
Jan Willem Mannaerts
fdd9ba8d56 Initial commit: Pokerface sprint planning poker for Jira
Full-stack app with Express/Socket.io backend, React frontend,
NATS JetStream for state, and Atlassian Jira OAuth integration.

Includes security hardening: NATS auth support, KV bucket TTL
enforcement, CAS retry for race conditions, error message
sanitization, and OAuth state stored in NATS KV.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 21:38:37 +01:00