summaryrefslogtreecommitdiff
path: root/src/cmd_auth.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-23 10:08:44 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-23 10:08:44 +0200
commite06248ef69b33d102a57aaa31fffcde3240dad9c (patch)
treed2b82eb5a5e2918336f89e241efdeed8bed5ca95 /src/cmd_auth.c
parent79b27457125dc2e259783359085021eea1d26e3a (diff)
downloadbokf-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.c73
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 = {