package main import ( "crypto/rand" "encoding/base64" "encoding/json" "log" "net/http" "strings" "time" ) func randomToken(n int) string { b := make([]byte, n) rand.Read(b) return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(b) } func jsonResp(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) } func jsonErr(w http.ResponseWriter, status int, msg string) { jsonResp(w, status, map[string]string{"error": msg}) } // Get email from session cookie func authEmail(r *http.Request) string { c, err := r.Cookie("v1984_session") if err != nil { return "" } email, err := sessionGet(c.Value) if err != nil { return "" } return email } func setSessionCookie(w http.ResponseWriter, token string) { http.SetCookie(w, &http.Cookie{ Name: "v1984_session", Value: token, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode, MaxAge: 86400, }) } func clearSessionCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: "v1984_session", Value: "", Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode, MaxAge: -1, }) } // POST /api/auth/email — send login code (stubbed: always 123456) func apiAuthEmail(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { jsonErr(w, 405, "method not allowed") return } var req struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Email == "" { jsonErr(w, 400, "email required") return } email := strings.ToLower(strings.TrimSpace(req.Email)) // Auto-create account if it doesn't exist exists, _, _ := accountGet(email) if !exists { if err := accountCreate(email); err != nil { log.Printf("account auto-create error: %v", err) jsonErr(w, 500, "internal error") return } log.Printf("account auto-created: %s", email) } // Store code (stub: always 123456) code := "123456" if err := loginCodeSet(email, code); err != nil { log.Printf("login code error: %v", err) jsonErr(w, 500, "internal error") return } log.Printf("login code for %s: %s (stub — not emailed)", email, code) jsonResp(w, 200, map[string]string{"status": "sent"}) } // POST /api/auth/verify — verify login code func apiAuthVerify(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { jsonErr(w, 405, "method not allowed") return } var req struct { Email string `json:"email"` Code string `json:"code"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Email == "" || req.Code == "" { jsonErr(w, 400, "email and code required") return } email := strings.ToLower(strings.TrimSpace(req.Email)) ok, err := loginCodeVerify(email, req.Code) if err != nil || !ok { jsonErr(w, 401, "invalid or expired code") return } token, err := sessionCreate(email) if err != nil { jsonErr(w, 500, "internal error") return } setSessionCookie(w, token) jsonResp(w, 200, map[string]string{"status": "ok"}) } // POST /api/auth/logout func apiAuthLogout(w http.ResponseWriter, r *http.Request) { if c, err := r.Cookie("v1984_session"); err == nil { sessionDelete(c.Value) } clearSessionCookie(w) jsonResp(w, 200, map[string]string{"status": "ok"}) } // POST /api/checkout — create account (stub: no Stripe yet) func apiCheckout(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { jsonErr(w, 405, "method not allowed") return } var req struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Email == "" { jsonErr(w, 400, "email required") return } email := strings.ToLower(strings.TrimSpace(req.Email)) // Create account (idempotent) if err := accountCreate(email); err != nil { log.Printf("account create error: %v", err) jsonErr(w, 500, "internal error") return } // Auto-login after checkout (stub — real flow goes through Stripe first) token, err := sessionCreate(email) if err != nil { jsonErr(w, 500, "internal error") return } setSessionCookie(w, token) log.Printf("account created (stub): %s", email) // In production: return Stripe checkout URL // For now: redirect to regions page jsonResp(w, 200, map[string]string{"url": basePath + "/regions"}) } // GET /api/vaults — list vaults for authenticated user func apiVaults(w http.ResponseWriter, r *http.Request) { email := authEmail(r) if email == "" { jsonErr(w, 401, "not authenticated") return } vaults, err := vaultList(email) if err != nil { jsonErr(w, 500, "internal error") return } if vaults == nil { vaults = []Vault{} } count, _ := vaultCount(email) jsonResp(w, 200, map[string]any{ "email": email, "vaults": vaults, "count": count, "max": 1, // consumer: 1 vault }) } // POST /api/vault/create func apiVaultCreate(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { jsonErr(w, 405, "method not allowed") return } email := authEmail(r) if email == "" { jsonErr(w, 401, "not authenticated") return } var req struct { VaultID string `json:"vault_id"` Region string `json:"region"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Region == "" { jsonErr(w, 400, "region required") return } // Check capacity count, _ := vaultCount(email) if count >= 1 { jsonErr(w, 409, "vault limit reached") return } // Generate vault ID if not provided (normally comes from PRF on the POP) vaultID := req.VaultID if vaultID == "" { vaultID = randomToken(4)[:6] // 6 chars, stub } if err := vaultCreate(vaultID, email, req.Region); err != nil { log.Printf("vault create error: %v", err) jsonErr(w, 500, "internal error") return } log.Printf("vault created: %s for %s in %s", vaultID, email, req.Region) jsonResp(w, 201, map[string]string{ "vault_id": vaultID, "region": req.Region, "expires_at": time.Now().AddDate(1, 0, 0).UTC().Format(time.RFC3339), }) } // POST /api/vault/{id}/delete func apiVaultDelete(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { jsonErr(w, 405, "method not allowed") return } email := authEmail(r) if email == "" { jsonErr(w, 401, "not authenticated") return } // Extract vault ID from path: /api/vault/{id}/delete parts := strings.Split(r.URL.Path, "/") var vaultID string for i, p := range parts { if p == "vault" && i+1 < len(parts) { vaultID = parts[i+1] break } } if vaultID == "" { jsonErr(w, 400, "vault_id required") return } if err := vaultDelete(vaultID, email); err != nil { jsonErr(w, 404, "vault not found") return } log.Printf("vault deleted: %s for %s", vaultID, email) jsonResp(w, 200, map[string]string{"status": "deleted"}) } // GET /api/account — account info func apiAccount(w http.ResponseWriter, r *http.Request) { email := authEmail(r) if email == "" { jsonErr(w, 401, "not authenticated") return } exists, stripeID, _ := accountGet(email) if !exists { jsonErr(w, 404, "account not found") return } count, _ := vaultCount(email) jsonResp(w, 200, map[string]any{ "email": email, "stripe_id": stripeID, "vaults": count, "max": 1, }) }