diff --git a/html/js/ui/paymentUI.js b/html/js/ui/paymentUI.js index 22477f7..56eec6e 100644 --- a/html/js/ui/paymentUI.js +++ b/html/js/ui/paymentUI.js @@ -18,9 +18,14 @@ const PaymentUI = { // Bind mode options (use delegation for dynamic content) $('#payment-ui').on('click', '.mode-option', function() { + // The "Buy Selected Package" button ALSO carries .mode-option (for styling) but has its + // own handler below; without this guard it double-fired here with NO data attrs, defaulting + // to single_game — so buying a 10-credit pack ALSO created a spurious 1-credit invoice. + if (this.id === 'buy-credits-btn') return; const mode = $(this).data('mode'); const action = $(this).data('action'); const packageId = $(this).data('package-id'); + if (!mode && !action) return; // stray click with no intent PaymentUI.handleModeSelection(mode, action, packageId); }); diff --git a/src/routes/auth.js b/src/routes/auth.js index 278823f..e097c11 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -251,14 +251,20 @@ router.get('/api/auth/smirk/status', asyncHandler(async (req, res) => { return res.status(401).json({ error: 'Session token required' }); } + // Identify the caller by anon_token ALONE — it is the ownership secret. The previous query also + // required socket_id = current socket.id, but socket_id is VOLATILE (changes on every refresh / + // reconnect), so after a refresh the row's stored socket_id no longer matched and this 403'd — + // which left the client's _isLinked=false and silently disabled Smirk one-click payment. Matching + // on the unguessable token is the same guarantee (you can only read your OWN state) without the + // socket_id fragility. (socketId is still required in the query string for API shape.) const result = await db.query(` SELECT smirk_public_key, payout_address FROM users - WHERE socket_id = $1 AND anon_token = $2 - `, [socketId, token]); + WHERE anon_token = $1 + `, [token]); if (result.rows.length === 0) { - // Do not disclose whether the socketId exists — ownership check failed. + // Unknown/again-rotated token — ownership check failed; disclose nothing. return res.status(403).json({ error: 'Session ownership verification failed' }); }