Add typed account and password settings

- Add admin account list, create, role, delete, and password workflows
- Execute identity-checked mutations through the existing dispatcher
- Bound queued credential lifetime and wipe transient secrets
- Add explicit password generation with saved-value acknowledgement
- Handle self-revocation and uncertain outcomes without automatic
  retries
- Register optional account routes without disrupting terminal
  transports
- Expand host regressions and document contracts and pending target
  checks

Validated host suites and pio run; hardware validation remains pending.
This commit is contained in:
2026-09-08 09:27:02 +02:00
parent 42548f6334
commit 94433ef975
30 changed files with 1864 additions and 48 deletions
+90 -12
View File
@@ -295,7 +295,7 @@ static esp_err_t set_record_password(stored_user_t *user,
return error;
}
static esp_err_t generate_password(user_database_generated_password_t *generated)
esp_err_t user_database_generate_password_value(user_database_generated_password_t *generated)
{
if (generated == NULL) {
return ESP_ERR_INVALID_ARG;
@@ -788,6 +788,25 @@ esp_err_t user_database_get_snapshot(user_database_snapshot_t *snapshot)
return ESP_OK;
}
esp_err_t user_database_get_accounts(user_database_accounts_t *accounts)
{
if (accounts == NULL) return ESP_ERR_INVALID_ARG;
memset(accounts, 0, sizeof(*accounts));
if (!s_initialized || s_mutex == NULL) return ESP_ERR_INVALID_STATE;
if (xSemaphoreTake(s_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
for (size_t i = 0; i < USER_DATABASE_MAX_USERS; ++i) {
const stored_user_t *user = &s_database.users[i];
if (!user->active) continue;
user_database_account_t *out = &accounts->users[accounts->count++];
out->user_id = user->user_id;
out->auth_generation = user->auth_generation;
out->role = (user_role_t)user->role;
memcpy(out->username, user->username, user->username_length);
}
xSemaphoreGive(s_mutex);
return ESP_OK;
}
static void fill_principal(const stored_user_t *user, user_auth_method_t method,
user_principal_t *principal)
{
@@ -986,7 +1005,7 @@ esp_err_t user_database_create_generated(
const uint8_t *username, size_t username_length, user_role_t role,
user_database_generated_password_t *generated_password)
{
esp_err_t error = generate_password(generated_password);
esp_err_t error = user_database_generate_password_value(generated_password);
if (error == ESP_OK) {
error = user_database_create(username, username_length, role,
generated_password->password,
@@ -1037,7 +1056,7 @@ esp_err_t user_database_bootstrap_admin(const uint8_t *password,
esp_err_t user_database_bootstrap_admin_generated(
user_database_generated_password_t *generated_password)
{
esp_err_t error = generate_password(generated_password);
esp_err_t error = user_database_generate_password_value(generated_password);
if (error == ESP_OK) {
error = user_database_bootstrap_admin(generated_password->password,
generated_password->password_length);
@@ -1062,14 +1081,26 @@ static esp_err_t mutate_user_begin(const uint8_t *username, size_t username_leng
return ESP_OK;
}
esp_err_t user_database_delete(const uint8_t *username, size_t username_length)
static bool target_matches_locked(const uint8_t *username, size_t length,
const user_database_account_t *expected)
{
if (!expected) return true;
int index = find_user(&s_database, username, length);
return index >= 0 && expected->user_id != 0 && expected->auth_generation != 0 &&
s_database.users[index].user_id == expected->user_id &&
s_database.users[index].auth_generation == expected->auth_generation;
}
static esp_err_t delete_user(const uint8_t *username, size_t username_length,
const user_database_account_t *expected)
{
if (!s_initialized || s_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(s_mutex, portMAX_DELAY);
int index;
esp_err_t error = mutate_user_begin(username, username_length, &index);
esp_err_t error = target_matches_locked(username, username_length, expected)
? mutate_user_begin(username, username_length, &index) : ESP_ERR_NOT_FOUND;
if (error == ESP_OK) {
const stored_user_t *user = &s_database.users[index];
bool protected_migrated_admin =
@@ -1090,8 +1121,8 @@ esp_err_t user_database_delete(const uint8_t *username, size_t username_length)
return error;
}
esp_err_t user_database_set_role(const uint8_t *username, size_t username_length,
user_role_t role)
static esp_err_t set_role(const uint8_t *username, size_t username_length,
user_role_t role, const user_database_account_t *expected)
{
if (!s_initialized || s_mutex == NULL ||
(role != USER_ROLE_USER && role != USER_ROLE_ADMIN)) {
@@ -1099,7 +1130,8 @@ esp_err_t user_database_set_role(const uint8_t *username, size_t username_length
}
xSemaphoreTake(s_mutex, portMAX_DELAY);
int index;
esp_err_t error = mutate_user_begin(username, username_length, &index);
esp_err_t error = target_matches_locked(username, username_length, expected)
? mutate_user_begin(username, username_length, &index) : ESP_ERR_NOT_FOUND;
if (error == ESP_OK) {
stored_user_t *user = &s_candidate->users[index];
if (user->role == role) {
@@ -1125,8 +1157,37 @@ esp_err_t user_database_set_role(const uint8_t *username, size_t username_length
return error;
}
esp_err_t user_database_set_password(const uint8_t *username, size_t username_length,
const uint8_t *password, size_t password_length)
esp_err_t user_database_delete(const uint8_t *username, size_t length)
{
return delete_user(username, length, NULL);
}
esp_err_t user_database_set_role(const uint8_t *username, size_t length, user_role_t role)
{
return set_role(username, length, role, NULL);
}
esp_err_t user_database_delete_current(const user_database_account_t *expected)
{
if (!expected) return ESP_ERR_INVALID_ARG;
size_t length = strnlen(expected->username, sizeof(expected->username));
if (!user_database_username_valid((const uint8_t *)expected->username, length))
return ESP_ERR_INVALID_ARG;
return delete_user((const uint8_t *)expected->username, length, expected);
}
esp_err_t user_database_set_role_current(const user_database_account_t *expected, user_role_t role)
{
if (!expected) return ESP_ERR_INVALID_ARG;
size_t length = strnlen(expected->username, sizeof(expected->username));
if (!user_database_username_valid((const uint8_t *)expected->username, length))
return ESP_ERR_INVALID_ARG;
return set_role((const uint8_t *)expected->username, length, role, expected);
}
static esp_err_t set_password(const uint8_t *username, size_t username_length,
const uint8_t *password, size_t password_length,
const user_database_account_t *expected)
{
if (!s_initialized || s_mutex == NULL ||
!user_database_password_valid(password, password_length)) {
@@ -1134,7 +1195,8 @@ esp_err_t user_database_set_password(const uint8_t *username, size_t username_le
}
xSemaphoreTake(s_mutex, portMAX_DELAY);
int index;
esp_err_t error = mutate_user_begin(username, username_length, &index);
esp_err_t error = target_matches_locked(username, username_length, expected)
? mutate_user_begin(username, username_length, &index) : ESP_ERR_NOT_FOUND;
if (error == ESP_OK) {
stored_user_t *user = &s_candidate->users[index];
error = set_record_password(user, password, password_length);
@@ -1151,11 +1213,27 @@ esp_err_t user_database_set_password(const uint8_t *username, size_t username_le
return error;
}
esp_err_t user_database_set_password(const uint8_t *username, size_t username_length,
const uint8_t *password, size_t password_length)
{
return set_password(username, username_length, password, password_length, NULL);
}
esp_err_t user_database_set_password_current(const user_database_account_t *expected,
const uint8_t *password, size_t password_length)
{
if (!expected) return ESP_ERR_INVALID_ARG;
size_t length = strnlen(expected->username, sizeof(expected->username));
if (!user_database_username_valid((const uint8_t *)expected->username, length))
return ESP_ERR_INVALID_ARG;
return set_password((const uint8_t *)expected->username, length, password, password_length, expected);
}
esp_err_t user_database_generate_password(
const uint8_t *username, size_t username_length,
user_database_generated_password_t *generated_password)
{
esp_err_t error = generate_password(generated_password);
esp_err_t error = user_database_generate_password_value(generated_password);
if (error == ESP_OK) {
error = user_database_set_password(username, username_length,
generated_password->password,