feat(#2302): fold is_plain_ident into PlainIdent newtype
This commit is contained in:
parent
cfac917b4d
commit
d4e91bfeeb
3 changed files with 112 additions and 86 deletions
|
|
@ -23,7 +23,7 @@ use axum::extract::{Form, Query};
|
|||
use axum::response::{IntoResponse, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::error_response;
|
||||
use super::{error_response, idents::PlainIdent};
|
||||
use crate::coordinator::Coordinator;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -176,17 +176,6 @@ struct MatrixLoginResult {
|
|||
user_id: String,
|
||||
}
|
||||
|
||||
/// Plain-identifier check matching hive-priv's `validate_name_chars`
|
||||
/// exactly (lowercase ascii + digits + hyphens) — the root-side guard
|
||||
/// re-applies the same rule before building the token path. Keeping the
|
||||
/// dashboard check identical means a name that passes here can't then be
|
||||
/// rejected at the priv boundary with a confusing "write token failed".
|
||||
fn is_plain_ident(s: &str) -> bool {
|
||||
!s.is_empty()
|
||||
&& s.chars()
|
||||
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
|
||||
}
|
||||
|
||||
/// Provision (or refresh) the token for an agent's extra matrix account.
|
||||
/// password mode → `m.login.password`; token mode → validate via `whoami`.
|
||||
/// On success writes the token to `matrix-token-<account>` via hive-priv and
|
||||
|
|
@ -196,15 +185,15 @@ pub(super) async fn post_matrix_account_login(Form(f): Form<MatrixLoginForm>) ->
|
|||
let agent = f.agent.trim();
|
||||
let account = f.account.trim();
|
||||
let homeserver = f.homeserver.trim().trim_end_matches('/');
|
||||
if !is_plain_ident(agent) {
|
||||
let Ok(agent) = PlainIdent::parse(agent) else {
|
||||
return error_response(&format!("matrix-account-login: invalid agent {agent:?}"));
|
||||
}
|
||||
if !is_plain_ident(account) {
|
||||
};
|
||||
let Ok(account) = PlainIdent::parse(account) else {
|
||||
return error_response(&format!(
|
||||
"matrix-account-login: invalid account {account:?}"
|
||||
));
|
||||
}
|
||||
if account == "main" {
|
||||
};
|
||||
if account.as_str() == "main" {
|
||||
return error_response(
|
||||
"matrix-account-login: 'main' is the hive-internal account; it is \
|
||||
provisioned via the normal flow, not this form",
|
||||
|
|
@ -244,15 +233,19 @@ pub(super) async fn post_matrix_account_login(Form(f): Form<MatrixLoginForm>) ->
|
|||
}
|
||||
};
|
||||
|
||||
if let Err(e) =
|
||||
crate::priv_client::write_agent_matrix_token(agent, &token, Some(account), Some(homeserver))
|
||||
.await
|
||||
if let Err(e) = crate::priv_client::write_agent_matrix_token(
|
||||
agent.as_str(),
|
||||
&token,
|
||||
Some(account.as_str()),
|
||||
Some(homeserver),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return error_response(&format!("matrix-account-login: write token failed: {e:#}"));
|
||||
}
|
||||
// Best-effort kick so the daemon picks up the new account without a full
|
||||
// container restart; not fatal if the container isn't running.
|
||||
if let Err(e) = crate::priv_client::restart_matrix_daemon(agent).await {
|
||||
if let Err(e) = crate::priv_client::restart_matrix_daemon(agent.as_str()).await {
|
||||
tracing::warn!(
|
||||
%agent, %account, error = ?e,
|
||||
"matrix-account-login: daemon restart failed (token written; loads on next start)"
|
||||
|
|
@ -288,13 +281,13 @@ struct GithubAccountResult {
|
|||
pub(super) async fn post_github_account(Form(f): Form<GithubAccountForm>) -> Response {
|
||||
let agent = f.agent.trim();
|
||||
let token = f.token.trim();
|
||||
if !is_plain_ident(agent) {
|
||||
let Ok(agent) = PlainIdent::parse(agent) else {
|
||||
return error_response(&format!("github-account: invalid agent {agent:?}"));
|
||||
}
|
||||
};
|
||||
if token.is_empty() {
|
||||
return error_response("github-account: token is required");
|
||||
}
|
||||
if let Err(e) = crate::priv_client::write_agent_github_token(agent, token).await {
|
||||
if let Err(e) = crate::priv_client::write_agent_github_token(agent.as_str(), token).await {
|
||||
return error_response(&format!("github-account: write token failed: {e:#}"));
|
||||
}
|
||||
tracing::info!(%agent, "github-account: provisioned github PAT");
|
||||
|
|
@ -320,10 +313,10 @@ struct GithubAccountStatus {
|
|||
/// Never returns the token itself.
|
||||
pub(super) async fn get_github_account(Query(q): Query<GithubAccountQuery>) -> Response {
|
||||
let agent = q.agent.trim();
|
||||
if !is_plain_ident(agent) {
|
||||
let Ok(agent) = PlainIdent::parse(agent) else {
|
||||
return error_response(&format!("github-account: invalid agent {agent:?}"));
|
||||
}
|
||||
let present = Coordinator::agent_notes_dir(agent)
|
||||
};
|
||||
let present = Coordinator::agent_notes_dir(agent.as_str())
|
||||
.join("github-token")
|
||||
.exists();
|
||||
axum::Json(GithubAccountStatus { present }).into_response()
|
||||
|
|
@ -402,7 +395,7 @@ async fn matrix_whoami(homeserver: &str, token: &str) -> Result<String, String>
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{account_name_from_filename, is_plain_ident, read_accounts_snapshot};
|
||||
use super::{account_name_from_filename, read_accounts_snapshot};
|
||||
|
||||
fn unique_dir(tag: &str) -> std::path::PathBuf {
|
||||
let d = std::env::temp_dir().join(format!(
|
||||
|
|
@ -488,20 +481,4 @@ mod tests {
|
|||
assert_eq!(account_name_from_filename("notes.md"), None);
|
||||
assert_eq!(account_name_from_filename("matrix-avatar-icon-hash"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_plain_ident_matches_validate_name_chars() {
|
||||
// Accepts exactly what hive-priv's validate_name_chars does:
|
||||
// lowercase ascii + digits + hyphens.
|
||||
assert!(is_plain_ident("catgirl"));
|
||||
assert!(is_plain_ident("acct-1"));
|
||||
assert!(!is_plain_ident(""));
|
||||
// Rejected: uppercase + underscore (would pass a looser check
|
||||
// then fail at the priv boundary), and path chars.
|
||||
assert!(!is_plain_ident("MyAccount"));
|
||||
assert!(!is_plain_ident("my_account"));
|
||||
assert!(!is_plain_ident("../escape"));
|
||||
assert!(!is_plain_ident("a/b"));
|
||||
assert!(!is_plain_ident("a.b"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue