using Spectre.Console; using ZB.MOM.WW.Secrets.Abstractions; namespace ZB.MOM.WW.Secrets.Cli.Interactive.Flows; /// /// Reveals a secret's decrypted plaintext on screen — the one flow whose purpose is to surface a value. /// It gates the disclosure behind an explicit confirmation: on decline nothing is printed; on accept the /// value is decrypted with the session cipher and written exactly once (no caching, no resolver). Requires /// a KEK-capable session. /// public sealed class RevealSecretFlow : IInteractiveFlow { /// public string Title => "Get (reveal) a secret"; /// public bool RequiresKek => true; /// public async Task RunAsync(IAnsiConsole console, SecretsSession session, CancellationToken ct) { ArgumentNullException.ThrowIfNull(console); ArgumentNullException.ThrowIfNull(session); var name = new SecretName(console.Prompt( new TextPrompt("Secret [green]name[/]:").Validate(NameValidation))); StoredSecret? row = await session.Store.GetAsync(name, ct).ConfigureAwait(false); if (row is null || row.IsDeleted) { console.MarkupLineInterpolated($"[red]No secret named '{name.Value}'.[/]"); return; } if (!console.Prompt(new ConfirmationPrompt("Reveal the value on screen?") { DefaultValue = false })) { return; } console.WriteLine(session.Cipher!.Decrypt(row)); } // Re-prompts on an invalid name by surfacing the SecretName constructor's own diagnostic. private static ValidationResult NameValidation(string raw) { try { _ = new SecretName(raw); return ValidationResult.Success(); } catch (ArgumentException ex) { return ValidationResult.Error(ex.Message); } } }