Compare commits

..

5 Commits

Author SHA1 Message Date
will.anderson 708dfd06cb Merge pull request 'Fix magic-link sign-in: implicit flow + redirect to /account' (#138) from fix/magic-link-flow into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m14s
2026-05-12 19:33:03 +00:00
will.anderson b6aecd7d89 Fix magic-link sign-in: implicit flow + redirect to /account
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m42s
account-auth.el was using flowType:'pkce' while account-dashboard.el
uses 'implicit'. After the OTP redirect, the dashboard's implicit
client couldn't exchange the PKCE code — so the sign-in silently
failed. Fix: match implicit flow across both clients.

Also adds emailRedirectTo so the link lands on /account instead of
the site root.
2026-05-12 14:32:39 -05:00
will.anderson bb98f76179 Merge pull request 'Fix duplicate Stripe customers and attestation plan bypass' (#136) from fix/stripe-dedup-attestation into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m4s
2026-05-12 19:23:33 +00:00
will.anderson 0fdbba82e0 Fix duplicate Stripe customers and attestation plan bypass
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m29s
Two bugs:

1. Double-Bearer auth on Stripe customer search. Both checkout paths
   were passing "Bearer sk_..." to http_get_auth(), which prepends
   another "Bearer " — producing "Bearer Bearer sk_..." which Stripe
   rejects as 401. Customer lookup always failed, so a new Stripe
   customer was created on every checkout page load. Fix: pass the
   raw key to http_get_auth(), letting it handle the prefix.

2. /api/attest blindly wrote whatever plan the client submitted to
   the waitlist, letting anyone POST plan=founding and get founding
   member access without paying. Fix: server ignores the client-
   submitted plan and always writes plan=waitlist. Founding access
   requires Stripe payment — the attestation form is waitlist-only.
2026-05-12 14:10:04 -05:00
will.anderson 9e0451be41 Merge pull request 'Fix initStripe load order, subscription webhook email, chat textarea' (#134) from fix/webhook-initstripe-textarea into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m11s
Merge PR #134: Fix initStripe load order, webhook user_metadata, chat textarea
2026-05-12 17:49:26 +00:00
2 changed files with 11 additions and 5 deletions
+5 -2
View File
@@ -11,7 +11,7 @@ fn main() -> Void {
'use strict';
var cfg = window.NEURON_CFG || {};
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
auth: { flowType: 'pkce' }
auth: { flowType: 'implicit' }
});
window.sendMagicLink = async function() {
@@ -25,7 +25,10 @@ fn main() -> Void {
return;
}
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
var result = await sb.auth.signInWithOtp({ email: email });
var result = await sb.auth.signInWithOtp({
email: email,
options: { emailRedirectTo: window.location.origin + '/account' }
});
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
msgEl.style.display = 'block';
if (result.error) {
+6 -3
View File
@@ -686,7 +686,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: 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_search: String = http_get_auth(pi_search_url, stripe_key)
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")
@@ -784,7 +784,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
// 1. Search existing customers by email
let lc_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + lc_email_enc + "%22&limit=1"
let lc_search: String = http_get_auth(lc_search_url, lc_auth)
let lc_search: String = http_get_auth(lc_search_url, stripe_key)
let lc_cus_id: String = json_get_string(lc_search, "id")
// 2. If none, create one. We always include supabase_user_id so the
@@ -1116,13 +1116,16 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
}
let attest_name: String = json_get(body, "name")
let attest_email: String = json_get(body, "email")
let attest_plan: String = json_get(body, "plan")
let attest_ts: String = json_get(body, "timestamp")
let attest_text: String = json_get(body, "attestation")
let attest_ua: String = json_get(body, "user_agent")
if str_eq(attest_email, "") {
return "{\"error\":\"email required\"}"
}
// Founding membership now requires $199 Stripe payment the attestation
// form is a waitlist-only path. Server enforces this regardless of what
// the client submits as plan to prevent bypassing payment.
let attest_plan: String = "waitlist"
let n_safe: String = str_replace(str_replace(attest_name, "\\", "\\\\"), "\"", "\\\"")
let e_safe: String = str_replace(str_replace(attest_email, "\\", "\\\\"), "\"", "\\\"")
let t_safe: String = str_replace(str_replace(attest_text, "\\", "\\\\"), "\"", "\\\"")