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' });
}