Skip to content

Reveal temp passwords (non-prod) and mask OTP/reset destinations - #179

Open
goshacodes wants to merge 5 commits into
mainfrom
feat/reveal-temp-password
Open

Reveal temp passwords (non-prod) and mask OTP/reset destinations#179
goshacodes wants to merge 5 commits into
mainfrom
feat/reveal-temp-password

Conversation

@goshacodes

@goshacodes goshacodes commented Aug 15, 2026

Copy link
Copy Markdown
Member

Summary

Two related admin-console/UX changes, both gated so nothing extra is exposed in production:

  1. A show delivery channel for password reset, letting admins reveal a generated temporary password directly in the console instead of only delivering it by email/SMS — available only outside production.
  2. Masked destination hints for OTP delivery ("We sent a verification code to j***n@example.com") instead of the previous generic "entered credential" copy, using a new shared Masking utility.

Part 1 — "show" delivery channel

auth

  • DeliveryChannel gains show.
  • New PasswordRevealForbidden error.
  • PasswordService.resetPassword returns Option[Password]: plaintext for show, None for email/sms. Fails with PasswordRevealForbidden if show is requested while env.isProd.
  • UserController returns 200 {"password": ...} or 204.

central

  • DeliveryChannel.show + response payload threaded through AuthClient / UserService / UserController.
  • UserController re-rejects show in prod (404), mirroring the existing guard, so a tampered client can't reach the reveal path even if central's own check were bypassed.

edge

  • PermissionsResponse now reports isProd, sourced from EnvName, so the console can hide non-prod-only affordances.

central-ui

  • admin-app reads isProd from /permissions/me (defaults to true on load failure) and passes canRevealPassword down.
  • users-list offers a "Show — display it here" option only when non-prod, then displays the password masked behind a "Show password" checkbox with a copy-to-clipboard action.

Part 2 — masked OTP destination

util

  • New versola.util.Masking with email/phone, following industry conventions: email keeps the first/last char of the local part (j***n@example.com, fully masked if ≤ 2 chars); phone keeps the country calling code and last two digits (+1********34).

auth

  • StepView.Otp gains a masked destination: Option[String], computed in ConversationRenderService from the conversation's resolved credential (ConversationRecord.credential) before it's serialized into window.__VERSOLA_FORM__ — the unmasked value never reaches the client. Covers both a user-entered credential and a login+password flow's OTP-as-second-factor.

central-ui

  • otp.tsx renders "We sent a verification code to {destination}", falling back to the previous generic copy when no destination is available (form previews, since destination isn't a backend-configurable property). i18n added for en/ru.

central BootstrapService

  • defaultForms gains an overrideOnBoot flag so a form already present can still be re-seeded (as a new version) when its shipped resources changed, without touching every other form a tenant may have customized. otp opts in, so this change reaches existing environments. The decision is a pure shouldCheckForSeed function with unit tests, and re-seeding is a no-op if the shipped content is unchanged (so restarts don't churn out empty versions).

Testing

  • Scala: updated PasswordServiceSpec, auth/central UserControllerSpec, AuthClientSpec, EdgeServiceSpec/EdgeControllerSpec, ConversationRenderServiceSpec (new destination-masking cases), BootstrapServiceSpec (new shouldCheckForSeed cases); new MaskingSpec.
  • central-ui: updated Playwright mocks/fixtures; added Reset Password offers the show channel outside production / hides the show channel in production. Full Playwright suite passes except two app-shell.spec.ts failures that pre-exist on main (unrelated).
  • Scala side was not compiled/tested locally (no JVM/sbt available in this sandbox) — please run sbt test before merging.

Lets admins reveal a generated temporary password directly in the
console instead of only delivering it by email/SMS, gated to
non-production environments end-to-end:

- auth: DeliveryChannel.show; PasswordService returns the plaintext
  for 'show' and rejects it in prod with PasswordRevealForbidden.
- central: mirrors the prod guard so a tampered client can't reach
  auth's reveal path even if central's own check were bypassed.
- edge: PermissionsResponse now reports isProd, sourced from EnvName.
- central-ui: users-list offers a 'Show — display it here' option
  only when the console reports non-prod, then displays the password
  masked behind a checkbox with a copy-to-clipboard action.

Tests updated in auth/central/edge specs and central-ui Playwright
suite (adds show-channel and prod-hides-show-channel cases).
@goshacodes goshacodes changed the title Add non-prod "show" delivery channel for password reset Reveal temp passwords (non-prod) and mask OTP/reset destinations Aug 15, 2026
@goshacodes
goshacodes marked this pull request as ready for review August 15, 2026 17:23
@augmentcode

augmentcode Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor
🤖 Augment PR Summary

Summary:

This PR adds non-production support for revealing a newly generated temporary password in the admin console.

It introduces a show password-delivery channel and threads its optional plaintext response through auth, central, and the UI.

Production guards in both auth and central reject that channel, while edge permissions expose isProd so the UI hides it by default.

The users page adds a reveal dialog with masking, explicit visibility control, and clipboard support.

OTP pages now receive an optional masked destination derived server-side from the resolved conversation credential.

The OTP form renders destination-specific copy and retains generic copy for previews or unresolved credentials.

A shared masking utility supplies email and phone display hints without serializing the raw credential to the browser.

Bootstrap form seeding now supports selectively rechecking opted-in built-in forms on startup.

The OTP form opts into that reseeding path so updated copy reaches existing installations without reseeding every form.

Scala and Playwright coverage was expanded for the reveal flow, environment guard, OTP destination, masking, and seed decision logic.

The PR notes that the Scala suite still needs to be compiled and run before merge.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread util/src/main/scala/versola/util/Masking.scala Outdated
Comment thread central/implementations/postgres/src/main/scala/versola/BootstrapService.scala Outdated
- util: new Masking.email/phone (industry-conventional masking:
  first+last char of the local part for email, country code + last
  two digits for phone) with a MaskingSpec unit suite.
- auth: StepView.Otp gains a masked destination, computed in
  ConversationRenderService from the conversation's resolved
  credential (email or phone) before it's serialized into
  window.__VERSOLA_FORM__ — the unmasked value never reaches the
  client. Covers both a user-entered credential and a login+password
  flow's OTP-as-second-factor (userEmail/userPhone already flow into
  ConversationRecord.credential upstream).
- central-ui: otp.tsx renders 'We sent a verification code to
  {destination}', falling back to the previous generic copy when no
  destination is available (i.e. previews, since destination isn't a
  backend-configurable property). i18n added for en/ru.
- central/BootstrapService: defaultForms gains an overrideOnBoot flag
  so a form already present can still be re-seeded (as a new version)
  when its shipped resources changed, without touching every other
  form a tenant may have customized. otp opts in to ship this change
  to existing environments. Extracted the pure decision as
  shouldCheckForSeed with unit tests.
@goshacodes
goshacodes force-pushed the feat/reveal-temp-password branch from 38de2e5 to f9a9c9a Compare August 15, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant