feat: configure API keys from the admin panel (Developer Settings) - #87
Open
jsnapoli1 wants to merge 1 commit into
Open
feat: configure API keys from the admin panel (Developer Settings)#87jsnapoli1 wants to merge 1 commit into
jsnapoli1 wants to merge 1 commit into
Conversation
Setting up a store currently means running `wrangler secret put` for
every key. That is a poor handoff for a shop owner who just wants to
turn on AI images or connect Stripe.
Adds a Developer Settings page for STRIPE_SECRET_KEY, GEMINI_API_KEY,
OPENROUTER_API_KEY, OPENROUTER_MODEL, SITE_URL and the admin password.
A Worker cannot write its own secrets — `env` is injected per request and
is read-only, and changing a Cloudflare secret needs an account-scoped
API token that has no business inside a store. So values are stored in
KV, and every runtime read goes through resolveSetting(), which checks
KV before `env`. A value set in the UI therefore genuinely takes effect
rather than being shadowed by a stale binding.
Clearing a field deletes the KV entry and restores the `env` value. That
is also the recovery path if a bad value is saved:
wrangler kv key delete developer:settings --namespace-id <id> --remote
The admin password gets extra handling, since 295ee74 deliberately moved
auth onto the ADMIN_PASSWORD binding:
- stored salted-and-hashed, never as recoverable plaintext
- changing it requires the current password, so an unattended session
cannot be used to take the store over
- DELETE drops the override and falls back to the binding
- it cannot be set through the generic settings route
Secret values are write-only over the API: responses report only whether
a key is set and where it came from, never the value.
Adds tests/integration/developer-settings.test.js (9 cases), covering
KV-over-env precedence, fallthrough on clear, the recovery path, and
that plaintext is never persisted. Full suite: 287 passing.
Claude-Session: https://claude.ai/code/session_015XLFFfHWNeazSuz6UcsQ4C
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Setting up a store means running
wrangler secret putfor every key. That's a poor handoff for a shop owner who just wants to turn on AI images or connect Stripe — it requires a terminal, the Wrangler CLI, and knowing the key names.Approach
A Developer Settings page for
STRIPE_SECRET_KEY,GEMINI_API_KEY,OPENROUTER_API_KEY,OPENROUTER_MODEL,SITE_URL, and the admin password.A Worker cannot write its own secrets.
envis injected per request and is read-only; changing a Cloudflare secret needs an account-scoped API token that has no business living inside a store. So values go to KV, and every runtime read goes throughresolveSetting(), which checks KV first,envsecond.That ordering is deliberate. If
envwon, a value typed into the UI would be silently shadowed by a stale binding — the setting would appear to save and do nothing.Clearing a field deletes the KV entry and restores the
envvalue. That's also the recovery path if a bad value is ever saved:The admin password
This one needs justifying, because 295ee74 ("authenticate admin login against ADMIN_PASSWORD secret") deliberately moved auth onto the binding and away from KV. Re-opening that door isn't something to do casually, so it's constrained:
password_hashthis replacesDELETE /passworddrops the override and falls back to the bindingenvwhenever no KV password is stored, so existing deploys are untouchedIf you'd still rather the password stay binding-only, I'm happy to drop that part and keep the rest — the API keys are the bulk of the UX win.
Security notes
envremains available for anyone who prefers it — a store that never opens the page behaves exactly as today.Verification
Adds
tests/integration/developer-settings.test.js(9 cases): KV-over-env precedence, fallthrough on clear, password change end-to-end (new password works and old env password stops working), the recovery path, and that plaintext is never persisted.npm run lint— 0 errors. Build clean; verified the admin bundle contains the new page and has no unresolved references.Notes
AdminDashboardrouter import fix from fix: import Routes and Route in AdminDashboard (admin panel renders blank) #84, because this PR touches that file and the panel can't render without it. Will rebase if fix: import Routes and Route in AdminDashboard (admin panel renders blank) #84 lands first.PRODUCT_LIMITis deliberately not included — it already has a store-settings path, and a second one would mean two UIs for one value.GEMINI_IMAGE_MODELis not included here either; it arrives with fix: move image generation off the shut-down Gemini model #86.https://claude.ai/code/session_015XLFFfHWNeazSuz6UcsQ4C