Merge stage fixes into dev — HAVE_CURL, free-tier checkout, Stripe dedup, escaped styles
This commit is contained in:
+104
-29
@@ -485,7 +485,7 @@ fn config_get(key: String) -> String {
|
||||
// function - it serves __html_file__ directly with text/html.
|
||||
// This handler covers /api/* and /brand/* routes.
|
||||
|
||||
fn handle_request(method: String, path: String, body: String) -> String {
|
||||
fn handle_request_inner(method: String, path: String, body: String) -> String {
|
||||
let src_dir: String = state_get("__src_dir__")
|
||||
|
||||
// ── Root — serve El-generated landing page ────────────────────────────────
|
||||
@@ -503,7 +503,7 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
|
||||
// ── robots.txt ────────────────────────────────────────────────────────────
|
||||
if str_eq(path, "/robots.txt") {
|
||||
return "User-agent: *\nAllow: /\nSitemap: https://neurontechnologies.ai/sitemap.xml\n"
|
||||
return "User-agent: *\nAllow: /\n"
|
||||
}
|
||||
|
||||
// ── About page ────────────────────────────────────────────────────────────
|
||||
@@ -567,23 +567,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
}
|
||||
let timing: String = json_get_string(body, "timing")
|
||||
if str_eq(timing, "") { let timing = "now" }
|
||||
// Free tier: SetupIntent — save card details without charging.
|
||||
// Card is stored on a Stripe Customer; billing begins only if the
|
||||
// user later upgrades to a paid plan.
|
||||
// Free tier: no card required. Return immediately — no Stripe interaction.
|
||||
if str_eq(plan, "free") {
|
||||
let si_body: String = "automatic_payment_methods[enabled]=true"
|
||||
+ "&usage=off_session"
|
||||
+ "&metadata[plan]=free"
|
||||
let auth_header: String = "Bearer " + stripe_key
|
||||
let si_resp: String = http_post_form_auth(
|
||||
"https://api.stripe.com/v1/setup_intents",
|
||||
si_body,
|
||||
auth_header)
|
||||
if str_starts_with(si_resp, "{") {
|
||||
let inner: String = str_slice(si_resp, 1, str_len(si_resp))
|
||||
return "{\"setup_mode\":true,\"plan\":\"free\"," + inner
|
||||
}
|
||||
return si_resp
|
||||
return "{\"plan\":\"free\",\"free\":true,\"no_payment_required\":true}"
|
||||
}
|
||||
// Hard cap: block founding checkouts when 1,000 spots are filled
|
||||
if str_eq(plan, "founding") {
|
||||
@@ -595,6 +581,27 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
}
|
||||
let auth_header: String = "Bearer " + stripe_key
|
||||
|
||||
// Find-or-create Stripe Customer by email upfront so every intent
|
||||
// is attached to an existing customer — prevents duplicate customers.
|
||||
let pi_email: String = json_get_string(body, "email")
|
||||
let pi_name: String = json_get_string(body, "name")
|
||||
let pi_cus_id: String = ""
|
||||
if !str_eq(pi_email, "") {
|
||||
let pi_email_enc: String = str_replace(str_replace(pi_email, "@", "%40"), "+", "%2B")
|
||||
let pi_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + pi_email_enc + "%22&limit=1"
|
||||
let pi_search: String = http_get_auth(pi_search_url, auth_header)
|
||||
let pi_cus_id = json_get_string(pi_search, "id")
|
||||
if str_eq(pi_cus_id, "") {
|
||||
let pi_name_enc: String = str_replace(pi_name, " ", "%20")
|
||||
let pi_cus_body: String = "email=" + pi_email_enc
|
||||
+ "&name=" + pi_name_enc
|
||||
+ "&metadata[plan]=" + plan
|
||||
+ "&metadata[source]=neuron-checkout"
|
||||
let pi_cus_resp: String = http_post_form_auth("https://api.stripe.com/v1/customers", pi_cus_body, auth_header)
|
||||
let pi_cus_id = json_get_string(pi_cus_resp, "id")
|
||||
}
|
||||
}
|
||||
|
||||
// Setup-mode path: save payment method, do not charge. Only valid
|
||||
// for Professional (Founding is one-shot lifetime, charges immediately).
|
||||
if str_eq(plan, "professional") && str_eq(timing, "later") {
|
||||
@@ -603,6 +610,7 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
+ "&metadata[plan]=" + plan
|
||||
+ "&metadata[hold_until]=launch"
|
||||
+ "&metadata[launch_target]=2026-09-01"
|
||||
let si_body = if !str_eq(pi_cus_id, "") { si_body + "&customer=" + pi_cus_id } else { si_body }
|
||||
let si_resp: String = http_post_form_auth(
|
||||
"https://api.stripe.com/v1/setup_intents",
|
||||
si_body,
|
||||
@@ -625,6 +633,7 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
+ "&automatic_payment_methods[enabled]=true"
|
||||
+ "&metadata[plan]=" + plan
|
||||
+ "&metadata[timing]=" + timing
|
||||
let pi_body = if !str_eq(pi_cus_id, "") { pi_body + "&customer=" + pi_cus_id } else { pi_body }
|
||||
let response: String = http_post_form_auth(
|
||||
"https://api.stripe.com/v1/payment_intents",
|
||||
pi_body,
|
||||
@@ -973,12 +982,18 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
let ua_safe: String = str_replace(str_replace(attest_ua, "\\", "\\\\"), "\"", "\\\"")
|
||||
// Write to Supabase waitlist (attestation in dedicated column)
|
||||
waitlist_upsert(attest_email, attest_name, attest_plan, "founding-attestation", attest_text, attest_ua, 0)
|
||||
// Also save to GCS as immutable legal record
|
||||
// Also save to GCS as immutable legal record.
|
||||
// Written to the dedicated attestations bucket (GCS_ATTEST_BUCKET) which
|
||||
// is private and separate from the public-read shares bucket.
|
||||
let record: String = "{\"plan\":\"" + attest_plan + "\",\"name\":\"" + n_safe + "\",\"email\":\"" + e_safe + "\",\"timestamp\":\"" + attest_ts + "\",\"attestation\":\"" + t_safe + "\",\"user_agent\":\"" + ua_safe + "\"}"
|
||||
let gcs_bucket: String = env("GCS_SHARE_BUCKET")
|
||||
if !str_eq(gcs_bucket, "") {
|
||||
let attest_key: String = "attestations/" + attest_ts + "-" + attest_email + ".json"
|
||||
let gcs_ok: String = gcs_write(gcs_bucket, attest_key, record)
|
||||
let attest_bucket: String = env("GCS_ATTEST_BUCKET")
|
||||
if str_eq(attest_bucket, "") {
|
||||
// Fall back to share bucket with attestations/ prefix for legacy deploys
|
||||
let attest_bucket = env("GCS_SHARE_BUCKET")
|
||||
}
|
||||
if !str_eq(attest_bucket, "") {
|
||||
let attest_key: String = attest_ts + "-" + attest_email + ".json"
|
||||
let gcs_ok: String = gcs_write(attest_bucket, attest_key, record)
|
||||
println("[attest] gcs write " + attest_key + " -> " + gcs_ok)
|
||||
}
|
||||
// Email notification
|
||||
@@ -1100,15 +1115,21 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
}
|
||||
state_set(rl_key, int_to_str(rl_count + 1) + "|" + int_to_str(today_day))
|
||||
}
|
||||
// Turnstile: verify on first message only (tokens are single-use).
|
||||
// Per-message verification breaks chat flow. Forms get full verification.
|
||||
// Turnstile: server-side verification is mandatory on every first
|
||||
// message (tokens are single-use; per-message verification would
|
||||
// break streaming chat flow so only the first message carries one).
|
||||
// Requests without a cf_token are rejected outright — the widget
|
||||
// must execute successfully before the first POST is sent.
|
||||
let cf_token: String = json_get(body, "cf_token")
|
||||
if !str_eq(cf_token, "") {
|
||||
let ts_secret: String = "0x4AAAAAADHAZTok46L-l2sa9biSGpgN3GY"
|
||||
let ts_secret: String = state_get("__turnstile_secret_key__")
|
||||
if str_eq(cf_token, "") && !str_eq(ts_secret, "") {
|
||||
return "{\"error\":\"Bot check required. Please complete the challenge.\"}"
|
||||
}
|
||||
if !str_eq(cf_token, "") && !str_eq(ts_secret, "") {
|
||||
let verify_body: String = "secret=" + ts_secret + "&response=" + cf_token
|
||||
let verify_resp: String = http_post("https://challenges.cloudflare.com/turnstile/v0/siteverify", verify_body)
|
||||
let verify_resp: String = http_post("https://challenges.cloudflare.com/turnstile/v1/siteverify", verify_body)
|
||||
let is_valid: String = json_get(verify_resp, "success")
|
||||
if str_eq(is_valid, "false") {
|
||||
if !str_eq(is_valid, "true") {
|
||||
return "{\"error\":\"Bot check failed. Please try again.\"}"
|
||||
}
|
||||
}
|
||||
@@ -1600,9 +1621,15 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
} else {
|
||||
let share_html = fs_read(src_dir + "/shares/" + id + ".html")
|
||||
}
|
||||
// Guard against empty responses and GCS error JSON (e.g. {"error":...}).
|
||||
// A valid share card always starts with "<" (HTML). Anything else is
|
||||
// treated as a missing card to avoid leaking bucket names or GCS details.
|
||||
if str_eq(share_html, "") {
|
||||
return "{\"__status__\":404,\"error\":\"not found\"}"
|
||||
}
|
||||
if !str_starts_with(share_html, "<") {
|
||||
return "{\"__status__\":404,\"error\":\"not found\"}"
|
||||
}
|
||||
return share_html
|
||||
}
|
||||
|
||||
@@ -1810,6 +1837,52 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
return "{\"__status__\":404,\"error\":\"not found\"}"
|
||||
}
|
||||
|
||||
// ── Security header wrapper ───────────────────────────────────────────────────
|
||||
//
|
||||
// Injects mandatory security headers on every response. Called by
|
||||
// handle_request which is the actual http_set_handler target; the inner
|
||||
// dispatcher (handle_request_inner) returns plain bodies so all the existing
|
||||
// route code is unchanged.
|
||||
//
|
||||
// Headers applied:
|
||||
// Strict-Transport-Security — forces HTTPS for 2 years + preload
|
||||
// X-Content-Type-Options — no MIME sniffing
|
||||
// X-Frame-Options — no framing except same origin
|
||||
// Referrer-Policy — full URL within origin, origin-only cross-site
|
||||
// Permissions-Policy — deny geo/mic/camera
|
||||
// Content-Security-Policy — allow self + trusted CDNs used by the app
|
||||
|
||||
fn sec_headers_json() -> String {
|
||||
"{\"Strict-Transport-Security\":\"max-age=63072000; includeSubDomains; preload\","
|
||||
+ "\"X-Content-Type-Options\":\"nosniff\","
|
||||
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
||||
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
||||
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
||||
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
|
||||
}
|
||||
|
||||
fn handle_request(method: String, path: String, body: String) -> String {
|
||||
let inner_resp: String = handle_request_inner(method, path, body)
|
||||
// Detect envelope already set by inner handler (starts with
|
||||
// {"el_http_response":1). If so, let it pass through unmodified —
|
||||
// the status code it carries takes precedence and we must not
|
||||
// double-wrap. (Currently inner never returns an envelope, but guard
|
||||
// defensively so a future route returning http_response(...) still works.)
|
||||
if str_starts_with(inner_resp, "{\"el_http_response\":1") {
|
||||
return inner_resp
|
||||
}
|
||||
// Detect the __status__ convention used by many routes so we can forward
|
||||
// the correct HTTP status code while still injecting security headers.
|
||||
let status_code: Int = 200
|
||||
if str_starts_with(inner_resp, "{\"__status__\":") {
|
||||
let status_str: String = json_get(inner_resp, "__status__")
|
||||
if !str_eq(status_str, "") {
|
||||
let status_code = str_to_int(status_str)
|
||||
}
|
||||
}
|
||||
http_response(status_code, sec_headers_json(), inner_resp)
|
||||
}
|
||||
|
||||
// ── Startup ───────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Order matters:
|
||||
@@ -1839,6 +1912,7 @@ let resend_api_key: String = env("RESEND_API_KEY")
|
||||
let supabase_anon_key: String = env("SUPABASE_ANON_KEY")
|
||||
let supabase_service_key: String = env("SUPABASE_SERVICE_KEY")
|
||||
let supabase_project_url: String = "https://ocojsghaonltunidkzpw.supabase.co"
|
||||
let turnstile_secret_key: String = env("TURNSTILE_SECRET_KEY")
|
||||
|
||||
// Origin — drives Stripe redirect URLs; never hardcoded to localhost.
|
||||
let neuron_origin_env: String = env("NEURON_ORIGIN")
|
||||
@@ -1886,6 +1960,7 @@ state_set("__origin__", neuron_origin)
|
||||
state_set("__founding_sold_file__", sold_file)
|
||||
state_set("__founding_sold__", int_to_str(real_sold))
|
||||
state_set("__founding_total__", int_to_str(FOUNDING_TOTAL))
|
||||
state_set("__turnstile_secret_key__", turnstile_secret_key)
|
||||
persist_founding_count(real_sold)
|
||||
|
||||
println(color_bold("Neuron") + " - " + neuron_origin)
|
||||
|
||||
Reference in New Issue
Block a user