feat(billing): masked write-only Stripe/gateway key in the config UI #422

Merged
nrupard merged 2 commits from feat/mapps-363-stripe-masked-key into main 2026-07-09 17:51:43 +02:00
Owner

Implements MAPPS-363 (slice of PMS-636): an admin can enable Stripe and enter its API key from the payment-gateway config UI, write-only and never shown back.

What changed (mokosh-apps, client only)

The payment-gateway modal previously entered credentials as a raw "Config (JSON)" textarea and decoded a config field the server no longer returns. PMS-342 already made the secret write-only server-side (the GET exposes only a configured flag, never the plaintext), so the client was stale and, worse, editing a gateway to flip Active re-sent config: {} which the server encrypts and stores, silently wiping the real key.

  • Replaced the JSON textarea with a masked, write-only API-key password input. It always starts blank; on save config is sent only when a key was typed, so leaving it blank keeps the existing secret (PMS-342 omit-to-keep). A first-time gateway still requires a key, guarded client-side because the server 400s a create with no config.
  • Surfaced configured vs not-configured: a badge in the modal and a new "Credentials" column in the list.
  • RemoteGateway now decodes configured instead of the dropped config; save failures route through ApiError::user_message().

No server change: the encrypted payment_gateway_configs backend and the configured status already exist; this reuses PUT /payment-gateways (upsert) and GET /payment-gateways.

Acceptance criteria

  • Admin can enable/disable Stripe (the is_active toggle, unchanged).
  • Admin can enter the key; it is write-only and never rendered back in clear (masked password input, blank on edit, server never returns it).
  • UI shows configured vs not-configured state (modal badge + list column) and surfaces server errors.
  • Persists via the existing encrypted payment_gateway_configs path (no new secret store).

Notes

  • The credential field is a single masked "API key" mapping to config.api_key, which fully covers Stripe. Provider-specific multi-field config for Authorize.Net / PayPal (login id + transaction key, client id + secret) is out of scope here and would be a separate PMS-636 slice.
  • A masked last4 preview is not shown because the server exposes only a boolean configured, not a masked value. If a last4 display is wanted, it needs a small mokosh-server enhancement to PaymentGatewayConfigResponse.

No local Rust toolchain; relying on CI (clippy + fmt + wasm check + tests).

Implements MAPPS-363 (slice of PMS-636): an admin can enable Stripe and enter its API key from the payment-gateway config UI, write-only and never shown back. ## What changed (mokosh-apps, client only) The payment-gateway modal previously entered credentials as a raw "Config (JSON)" textarea and decoded a `config` field the server no longer returns. PMS-342 already made the secret write-only server-side (the GET exposes only a `configured` flag, never the plaintext), so the client was stale and, worse, editing a gateway to flip Active re-sent `config: {}` which the server encrypts and stores, silently wiping the real key. - Replaced the JSON textarea with a masked, write-only API-key `password` input. It always starts blank; on save `config` is sent only when a key was typed, so leaving it blank keeps the existing secret (PMS-342 omit-to-keep). A first-time gateway still requires a key, guarded client-side because the server 400s a create with no config. - Surfaced configured vs not-configured: a badge in the modal and a new "Credentials" column in the list. - `RemoteGateway` now decodes `configured` instead of the dropped `config`; save failures route through `ApiError::user_message()`. No server change: the encrypted `payment_gateway_configs` backend and the `configured` status already exist; this reuses `PUT /payment-gateways` (upsert) and `GET /payment-gateways`. ## Acceptance criteria - [x] Admin can enable/disable Stripe (the `is_active` toggle, unchanged). - [x] Admin can enter the key; it is write-only and never rendered back in clear (masked password input, blank on edit, server never returns it). - [x] UI shows configured vs not-configured state (modal badge + list column) and surfaces server errors. - [x] Persists via the existing encrypted `payment_gateway_configs` path (no new secret store). ## Notes - The credential field is a single masked "API key" mapping to `config.api_key`, which fully covers Stripe. Provider-specific multi-field config for Authorize.Net / PayPal (login id + transaction key, client id + secret) is out of scope here and would be a separate PMS-636 slice. - A masked last4 preview is not shown because the server exposes only a boolean `configured`, not a masked value. If a last4 display is wanted, it needs a small mokosh-server enhancement to `PaymentGatewayConfigResponse`. No local Rust toolchain; relying on CI (clippy + fmt + wasm check + tests).
feat(billing): masked write-only Stripe/gateway key in the config UI
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 2m13s
7833a508ea
The payment-gateway config modal entered credentials as a raw "Config (JSON)" textarea and decoded a `config` field the server no longer returns (PMS-342 made the secret write-only, exposing only a `configured` flag). Two problems: the plaintext key was editable/echo-shaped rather than write-only, and editing an existing gateway to toggle Active re-sent `config: {}`, which the server encrypts and stores, silently wiping the real key.

Replace the JSON textarea with a masked, write-only API-key `password` input: it always starts blank, and on save `config` is sent only when a key was typed, so a blank field keeps the existing secret (omit-to-keep). A first-time gateway still requires a key (guarded client-side, since the server 400s a create with no config). Surface configured vs not-configured with a badge in the modal and a new "Credentials" column, and route save failures through `user_message()`. Decode `configured` instead of the dropped `config`.

No server change: the write-only backend and `configured` status already exist.

#MAPPS-363

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(billing): associate the API-key label and align gateway status wording
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 2m6s
Create release / Create release from merged PR (pull_request) Has been skipped
c99a6bc9f1
Code-review follow-ups on the write-only gateway key: the key field's "API key" caption was a bare span, so it was not programmatically associated with the input; make it a `<label for="gateway_api_key">` like every sibling field. Also align the list column's status text with the modal ("Not configured" instead of "Missing").

#MAPPS-363

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-09 17:50:16 +02:00
nrupard deleted branch feat/mapps-363-stripe-masked-key 2026-07-09 17:51:43 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!422
No description provided.