View Issue Details

IDProjectCategoryView StatusLast Update
0010650Talerlibeufin-bankpublic2026-06-27 22:15
ReporterChristian Grothoff Assigned ToAntoine A  
PrioritynormalSeveritytweakReproducibilityalways
Status confirmedResolutionopen 
Platformi7OSDebian GNU/LinuxOS Versionsqueeze
Product Versiongit (master) 
Target Versionpost-1.0 
Summary0010650: libeufin-bank passwd requests password even if no username was given
DescriptionSee:
$ libeufin-bank passwd
Password:
Repeat password for confirmation:
Usage: libeufin-bank passwd [<options>] <username> [<password>]

Error: missing argument <username>
Tagspatch

Activities

Christian Grothoff

2026-06-27 22:15

manager   ~0029002

Attached is a proposed fix. @antoine: please review!
libeufin.diff (1,899 bytes)   
diff --git a/libeufin-bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt b/libeufin-bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt
index de012c1a..fae2c73b 100644
--- a/libeufin-bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt
+++ b/libeufin-bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt
@@ -36,12 +36,19 @@ class ChangePw : TalerCmd("passwd") {
     override fun help(context: Context) = "Change account password"
 
     private val username by argument("username", help = "Account username")
+    // Optional: when omitted we prompt interactively from run(), i.e. only
+    // after the required 'username' argument has been provided and validated.
+    // Prompting from an argument default (defaultLazy) would run during
+    // argument finalization and thus ask for the password even when 'username'
+    // is missing (see bug #10650).
     private val password by argument(
-        "password", 
+        "password",
         help = "Account password used for authentication"
-    ).defaultLazy("prompt") {
+    ).optional()
+
+    private fun promptPassword(): String {
         val terminal = Terminal()
-        ConfirmationPrompt.create(
+        return ConfirmationPrompt.create(
             "Password",
             "Repeat for confirmation",
             "Values do not match, try again",
@@ -58,9 +65,11 @@ class ChangePw : TalerCmd("passwd") {
             }
         ).ask()!!
     }
+
     override fun run() = cliCmd(logger) {
+        val rawPassword = password ?: promptPassword()
         bankConfig(config).withDb { db, cfg ->
-            val password = password.checkPw(cfg.pwdCheckQuality)
+            val password = rawPassword.checkPw(cfg.pwdCheckQuality)
             val res = db.account.reconfigPassword(username, password, null, true, cfg.pwCrypto)
             when (res) {
                 AccountPatchAuthResult.UnknownAccount ->
libeufin.diff (1,899 bytes)   
analysis.md (3,552 bytes)   
# Bug 10650 — `libeufin-bank passwd` prompts for a password even when no username is given

- **Repository:** `libeufin` (module: libeufin-bank)
- **Category:** libeufin-bank / Severity: tweak
- **Patch:** [`libeufin.diff`](./libeufin.diff)
- **Upstream:** https://bugs.gnunet.org/view.php?id=10650

## Core issue

Running the `passwd` subcommand without the required `username` argument
interactively asks for (and confirms) a new password *first*, and only
*afterwards* fails with the missing-argument error:

```
$ libeufin-bank passwd
Password:
Repeat password for confirmation:
Usage: libeufin-bank passwd [<options>] <username> [<password>]
Error: missing argument <username>
```

The user is forced to type a password twice for a command that was always
going to be rejected. The validation of the required `username` argument
should happen before any interactive prompting.

## Root cause

In `libeufin-bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt`, the
optional `password` argument supplied its default via Clikt's `defaultLazy`,
and that lambda performed the interactive `ConfirmationPrompt`:

```kotlin
private val username by argument("username", ...)
private val password by argument("password", ...)
    .defaultLazy("prompt") {
        ... ConfirmationPrompt.create(...).ask()!!   // prompts here
    }
```

Clikt evaluates argument defaults during **argument finalization**, which runs
*before* the framework raises `MissingArgument` for the absent required
`username`. So the prompt fires as a side effect of finalizing `password`,
ahead of the `username` check. Prompting from inside an argument default is the
core mistake: a default value's computation should be free of user-visible side
effects that depend on other arguments being present.

## Resolution

Make `password` a plain optional argument (no side-effecting default) and move
the interactive prompt into `run()`, which Clikt only invokes **after** all
required arguments have been successfully parsed and validated:

```kotlin
private val password by argument("password", ...).optional()

private fun promptPassword(): String { /* ConfirmationPrompt … */ }

override fun run() = cliCmd(logger) {
    val rawPassword = password ?: promptPassword()
    bankConfig(config).withDb { db, cfg ->
        val password = rawPassword.checkPw(cfg.pwdCheckQuality)
        ...
    }
}
```

With this change, invoking `passwd` with no `username` exits immediately with
the "missing argument <username>" error and never prompts. When `username`
*is* provided but `password` is omitted, the prompt behaves exactly as before.
The prompt is also now performed before the DB connection is opened, so no
connection is held open across the interactive wait.

The prompting logic itself (the `ConfirmationPrompt` with hidden input and
match confirmation) is preserved verbatim, just relocated into the
`promptPassword()` helper.

## Verification

- `.optional()` is the standard Clikt argument modifier and is already used the
  same way in a sibling command in this module
  (`cli/CreateAccount.kt:76`), so the API and the existing wildcard import
  `com.github.ajalt.clikt.parameters.arguments.*` cover it.
- A full Kotlin compile could **not** be run in this environment: the offline
  Gradle build aborts before compilation because it cannot fetch the
  `org.jetbrains.kotlin.jvm` Gradle plugin (no network / plugin not cached).
  The change is therefore validated by code review against the existing Clikt
  usage patterns in the same module rather than by compilation.
analysis.md (3,552 bytes)   

Issue History

Date Modified Username Field Change
2025-11-24 22:34 Christian Grothoff New Issue
2025-11-24 22:34 Christian Grothoff Status new => assigned
2025-11-24 22:34 Christian Grothoff Assigned To => Antoine A
2025-12-07 10:53 Christian Grothoff Status assigned => confirmed
2026-06-27 22:15 Christian Grothoff Note Added: 0029002
2026-06-27 22:15 Christian Grothoff File Added: libeufin.diff
2026-06-27 22:15 Christian Grothoff File Added: analysis.md
2026-06-27 22:15 Christian Grothoff Tag Attached: patch