proto(woo): per-browser Woo creds via X-Woo-* request headers

Prototype routing so 2–3 owners can share one store URL with different WP
application passwords. Device creds live in localStorage; server uses
request override → Settings fallback and never writes overrides into the
Settings row. Inference stays on shared Settings.

See PROTOTYPE.md for UAT with two browsers and known LAN forge risk.
This commit is contained in:
grokbot_ty
2026-09-26 12:26:21 -07:00
parent fc40c66c44
commit 2f6335ec26
8 changed files with 419 additions and 31 deletions
+8 -10
View File
@@ -12,7 +12,7 @@ import re
from datetime import datetime, timedelta, timezone
from typing import Any
from fastapi import APIRouter
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from .db import get_connection
@@ -23,6 +23,8 @@ from .woo import (
WooCredentialsMissing,
WooError,
WooForbiddenError,
client_for_request,
settings_woo_creds,
)
from .review import compute_ready, _slab_dict
@@ -139,7 +141,7 @@ def _classify(e: Exception) -> tuple[str, str, bool]:
@router.post("/{slab_id}/publish")
async def publish(slab_id: str) -> dict:
async def publish(slab_id: str, request: Request) -> dict:
conn = get_connection()
row = conn.execute("SELECT * FROM slabs WHERE id=? AND deleted_at IS NULL", (slab_id,)).fetchone()
if row is None:
@@ -150,9 +152,9 @@ async def publish(slab_id: str) -> dict:
if not ready:
return JSONResponse(status_code=422, content=_error("unpublishable", "Slab is not ready", {"missing": missing}))
# 2. SKU lookup (PUBLISH-15) — stop if it exists in the store.
base, user, enc = _woo_creds(conn)
# Per-request Woo creds: X-Woo-* headers → Settings fallback (never persisted).
try:
client = WooClient.from_settings(base, user, enc)
client = client_for_request(conn, request.headers)
except WooCredentialsMissing as e:
return JSONResponse(status_code=422, content=_error("woo_unconfigured", str(e)))
try:
@@ -221,12 +223,8 @@ async def publish(slab_id: str) -> dict:
def _woo_creds(conn) -> tuple[str, str | None, str | None]:
import os
base = os.environ.get("WOO_BASE_URL", "")
u = conn.execute("SELECT value FROM settings WHERE key='woo_wp_username'").fetchone()
p = conn.execute("SELECT value_enc FROM settings WHERE key='woo_app_password'").fetchone()
return base, (u[0] if u else None), (p[0] if p else None)
"""Shop-default Woo creds (Settings). Prefer client_for_request for HTTP paths."""
return settings_woo_creds(conn)
def _create_status(conn) -> str: