diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-23 10:08:44 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-23 10:08:44 +0200 |
| commit | e06248ef69b33d102a57aaa31fffcde3240dad9c (patch) | |
| tree | d2b82eb5a5e2918336f89e241efdeed8bed5ca95 /src/cmd_auth.c | |
| parent | 79b27457125dc2e259783359085021eea1d26e3a (diff) | |
| download | bokf-87be23e8c290019772ac206f965902a3b4fa35ce.tar.gz bokf-87be23e8c290019772ac206f965902a3b4fa35ce.zip | |
user.set_password: change your own password; TUI "Byt lösenord"v0.1.68
Needs the current password (wrong ones rate limited like logins) and a
password session, requires at least 10 characters, closes the user's
other sessions and is audited without secrets. The TUI main menu gets
"Byt lösenord" with masked prompts; ^R keeps working with the new
password. Masked prompt buffers are wiped before they are freed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/cmd_auth.c')
| -rw-r--r-- | src/cmd_auth.c | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/src/cmd_auth.c b/src/cmd_auth.c index 9eb628f..86dab12 100644 --- a/src/cmd_auth.c +++ b/src/cmd_auth.c @@ -414,6 +414,77 @@ static const struct cmd_arg args_session_use_org[] = { { "org", ARG_INT, 1, NULL, NULL, "Org id" }, }; +#define PASSWORD_MIN_LEN 10 + +/* The logged-in user changes their own password. Needs the current one + (wrong guesses count like failed logins) and a password session: a token + must not be able to take over the account. */ +static yyjson_mut_val *h_user_set_password(struct req *r) +{ + if (r->sess->token_id) + return fail(r, "FORBIDDEN", + "log in with your password to change it (not a token)"); + const char *cur = arg_str(r->args, "current_password"); + const char *pw = arg_str(r->args, "new_password"); + if (!cur || !pw) + return fail(r, "INVALID_ARGS", + "current_password and new_password are required"); + if (strlen(pw) < PASSWORD_MIN_LEN) + return failf(r, "INVALID_ARGS", + "new password must be at least %d characters", + PASSWORD_MIN_LEN); + if (strcmp(cur, pw) == 0) + return fail(r, "INVALID_ARGS", + "new password must differ from the current one"); + char key[64]; + snprintf(key, sizeof key, "pw:%lld", (long long)r->sess->user_id); + int64_t retry = 0; + if (rl_blocked(key, &retry)) + return failf(r, "RATE_LIMITED", + "too many wrong passwords; retry in %lld s", + (long long)retry); + + sqlite3_stmt *st = db_prepare_bound( + r->db, "SELECT pw_hash FROM users WHERE id=?1", "i", + r->sess->user_id); + if (!st) + return db_error(r); + char *hash = NULL; + if (sqlite3_step(st) == SQLITE_ROW && sqlite3_column_text(st, 0)) + hash = xstrdup((const char *)sqlite3_column_text(st, 0)); + sqlite3_finalize(st); + int ok = hash && auth_verify_password(hash, cur) == 0; + free(hash); + if (!ok) { + rl_fail(key); + audit_append(r->db, 0, r->sess->user_id, 0, "user.set_password", + "{}", "AUTH_FAILED", NULL); + return fail(r, "AUTH_FAILED", "current password is wrong"); + } + rl_ok(key); + + char *phc = NULL; + if (auth_hash_password(pw, &phc) != 0) + return fail(r, "INTERNAL", "password hashing failed"); + int rc = req_exec(r, "UPDATE users SET pw_hash=?1 WHERE id=?2", "si", phc, + r->sess->user_id); + free(phc); + if (rc != 0) + return NULL; + int closed = sessions_destroy_user(r->sess->user_id, r->sess->id); + audit_append(r->db, 0, r->sess->user_id, 0, "user.set_password", "{}", + "OK", NULL); + yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); + yyjson_mut_obj_add_int(r->rdoc, o, "sessions_closed", closed); + return o; +} + +static const struct cmd_arg args_user_set_password[] = { + { "current_password", ARG_STR, 1, NULL, NULL, "Current password" }, + { "new_password", ARG_STR, 1, NULL, NULL, + "New password, at least 10 characters" }, +}; + const struct command g_cmd_auth[] = { { "health", "Liveness probe", PERM_PUBLIC, 0, 0, 0, h_health, NULL, 0 }, { "meta", "Server metadata and limits", PERM_PUBLIC, 0, 0, 0, h_meta, NULL, @@ -428,6 +499,8 @@ const struct command g_cmd_auth[] = { 0, 0, 0, h_session_list_orgs, NULL, 0 }, { "session.use_org", "Switch active org", PERM_READ, 0, 0, 0, h_session_use_org, CMD_ARGS(args_session_use_org) }, + { "user.set_password", "Change your own password", PERM_READ, 0, 1, 0, + h_user_set_password, CMD_ARGS(args_user_set_password) }, }; const struct cmd_table g_cmd_table_auth = { |
