You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3.6 KiB
3.6 KiB
+++ title = "quotesdb/api: POST /api/admin/reset-auth-code endpoint" priority = 6 status = "done" ticket_type = "feature" dependencies = ["69a2c5"] +++
POST /api/admin/reset-auth-code endpoint
Add the admin-protected endpoint that replaces the stored admin auth code. The caller must supply the current code via X-Admin-Code. A new code may be provided in the request body; if omitted, the server generates a fresh 4-word passphrase.
Files to modify
src/bin/api/db/mod.rs— addupdate_admin_auth_codeto theQuoteRepositorytraitsrc/bin/api/db/d1.rs— implementupdate_admin_auth_codefor D1src/bin/api/db/native.rs— implementupdate_admin_auth_codefor native SQLitesrc/bin/api/handlers/mod.rs(or a newsrc/bin/api/handlers/admin.rs) — add thereset_auth_codehandlersrc/bin/api/main.rs— register the new route
New trait method (src/bin/api/db/mod.rs)
Add to the QuoteRepository trait:
/// Replace the admin auth code if `current` matches the stored value.
/// If `new_code` is `None`, generates a fresh 4-word passphrase.
/// Returns the new auth code string on success, or `DbError::Unauthorized`
/// if `current` does not match.
async fn update_admin_auth_code(
&self,
current: &str,
new_code: Option<&str>,
) -> Result<String, DbError>;
Implementation steps:
- Fetch the stored
admin_auth_codefromadmin_config. - If it does not match
current, returnDbError::Unauthorized(or a dedicated variant). - Determine the new code: use
new_codeif provided, otherwise call the existing passphrase-generation utility. - Write the new value to
admin_configwithUPDATE. - Return the new code string.
Request / response types
#[derive(Deserialize)]
struct ResetAuthCodeRequest {
new_code: Option<String>,
}
#[derive(Serialize)]
struct ResetAuthCodeResponse {
auth_code: String,
}
Handler
/// POST /api/admin/reset-auth-code
/// Requires X-Admin-Code header matching the stored admin passphrase.
/// Body: { "new_code": "optional-string" }
/// Response: 200 { "auth_code": "new-code" } or 403 on mismatch.
pub async fn reset_auth_code(
State(repo): State<Arc<dyn QuoteRepository>>,
headers: HeaderMap,
Json(payload): Json<ResetAuthCodeRequest>,
) -> impl IntoResponse {
let admin_code = match headers.get("x-admin-code").and_then(|v| v.to_str().ok()) {
Some(c) => c.to_owned(),
None => return StatusCode::FORBIDDEN.into_response(),
};
match repo.update_admin_auth_code(&admin_code, payload.new_code.as_deref()).await {
Ok(new_code) => Json(ResetAuthCodeResponse { auth_code: new_code }).into_response(),
Err(DbError::Unauthorized) => StatusCode::FORBIDDEN.into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
Route registration (src/bin/api/main.rs)
.route("/api/admin/reset-auth-code", post(handlers::reset_auth_code))
Tests
POST /api/admin/reset-auth-codewith correctX-Admin-Codeand no bodynew_code→200, response contains a non-emptyauth_codePOST /api/admin/reset-auth-codewith correctX-Admin-Codeand explicitnew_code→200,auth_codeequals the supplied valuePOST /api/admin/reset-auth-codewith wrongX-Admin-Code→403POST /api/admin/reset-auth-codewith missingX-Admin-Codeheader →403- After a successful reset, subsequent calls with the old code return
403and with the new code return200
Validation
cargo fmt && cargo check && cargo clippy && cargo test
Commit
feat(quotesdb): POST /api/admin/reset-auth-code endpoint