forked from ComputerTech/aprhodite
Fix #3: Remove client-exploitable payment endpoint
- Payment endpoint no longer uses @_require_auth (not client-callable) - Identifies user from webhook payload user_id instead of client JWT - Removed hardcoded payment secret from chat.js - Client now shows placeholder message directing to admin - Webhook secret + user_id must come from payment provider server
This commit is contained in:
parent
8da91ebf70
commit
be3503b31b
16
routes.py
16
routes.py
|
|
@ -291,12 +291,12 @@ def ai_message():
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
@api.route("/payment/success", methods=["POST"])
|
||||
@_require_auth
|
||||
def payment_success():
|
||||
"""
|
||||
Validate a payment webhook and flip user.has_ai_access.
|
||||
Server-side payment webhook – NOT callable by clients.
|
||||
|
||||
Expected body: { "secret": "<PAYMENT_SECRET>" }
|
||||
Validates the webhook secret and unlocks AI access for the user
|
||||
identified by the 'user_id' field in the JSON body.
|
||||
|
||||
For Stripe production: replace the secret comparison with
|
||||
stripe.Webhook.construct_event() using the raw request body and
|
||||
|
|
@ -312,7 +312,15 @@ def payment_success():
|
|||
):
|
||||
return jsonify({"error": "Invalid or missing payment secret"}), 403
|
||||
|
||||
user = g.current_user
|
||||
# Identify the user from the webhook payload (NOT from client auth)
|
||||
user_id = data.get("user_id")
|
||||
if not user_id:
|
||||
return jsonify({"error": "Missing user_id in webhook payload"}), 400
|
||||
|
||||
user = db.session.get(User, user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
if not user.has_ai_access:
|
||||
user.has_ai_access = True
|
||||
db.session.commit()
|
||||
|
|
|
|||
|
|
@ -546,30 +546,10 @@ $("tab-lobby").onclick = () => switchTab("lobby");
|
|||
|
||||
$("close-paywall").onclick = () => paywallModal.classList.add("hidden");
|
||||
$("unlock-btn").onclick = async () => {
|
||||
// Generate dummy secret for the stub endpoint
|
||||
// In production, this would redirect to a real payment gateway (Stripe)
|
||||
const secret = "change-me-payment-webhook-secret";
|
||||
const token = localStorage.getItem("sexychat_token");
|
||||
|
||||
try {
|
||||
const resp = await fetch("/api/payment/success", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ secret })
|
||||
});
|
||||
const res = await resp.json();
|
||||
if (res.status === "ok") {
|
||||
// socket event should handle UI unlock, but we can optimistically update
|
||||
state.hasAiAccess = true;
|
||||
updateVioletBadge();
|
||||
paywallModal.classList.add("hidden");
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Payment simulation failed.");
|
||||
}
|
||||
// In production, this redirects to a real payment gateway (Stripe Checkout).
|
||||
// The server-side webhook will unlock AI access after payment confirmation.
|
||||
// For now, show a placeholder message.
|
||||
alert("Payment integration coming soon. Contact the administrator to unlock Violet.");
|
||||
};
|
||||
|
||||
logoutBtn.onclick = () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue