Skip to content

Commit fe037fb

Browse files
author
aemiguel
committed
Agent display names separate from slugified IDs, bump v0.1.38
1 parent 3a3a4cc commit fe037fb

4 files changed

Lines changed: 76 additions & 14 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "lore-core"
3-
version = "0.1.37"
3+
version = "0.1.38"
44
edition = "2024"
55
autobins = false
66

src/api.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,7 +1585,7 @@ async fn create_agent_token(
15851585
let admin = require_admin(&state, &headers)?;
15861586
let owner = UserName::new(&payload.owner)?;
15871587
let created = state.auth.create_agent_token(NewAgentToken {
1588-
name: payload.name,
1588+
display_name: payload.name,
15891589
owner: owner.clone(),
15901590
grants: payload
15911591
.grants
@@ -2591,7 +2591,7 @@ async fn create_agent_from_ui(
25912591
// Validate the user can grant these permissions
25922592
validate_user_grants(&state, &session.user, &grants)?;
25932593
let created = state.auth.create_agent_token(NewAgentToken {
2594-
name: form.name.clone(),
2594+
display_name: form.name.clone(),
25952595
owner: session.user.username.clone(),
25962596
grants,
25972597
})?;
@@ -2602,12 +2602,12 @@ async fn create_agent_from_ui(
26022602
name: session.user.username.as_str().to_string(),
26032603
},
26042604
"create agent",
2605-
Some(form.name.clone()),
2605+
Some(created.stored.name.clone()),
26062606
None,
26072607
)?;
26082608
Ok(Redirect::to(&format!(
26092609
"/ui/agents?selected={}&created_token={}&flash=Agent%20created.%20Copy%20the%20token%20now.",
2610-
urlencoding::encode(&form.name),
2610+
urlencoding::encode(&created.stored.name),
26112611
urlencoding::encode(&created.token),
26122612
))
26132613
.into_response())
@@ -4586,8 +4586,13 @@ fn block_matches_filters(block: &Block, filters: &BlockFilterOptions) -> bool {
45864586
}
45874587

45884588
fn agent_token_summary(token: StoredAgentToken) -> AgentTokenSummary {
4589+
let display_name = token
4590+
.display_name
4591+
.clone()
4592+
.unwrap_or_else(|| token.name.clone());
45894593
AgentTokenSummary {
45904594
name: token.name,
4595+
display_name,
45914596
owner: token.owner.map(|u| u.as_str().to_string()),
45924597
grants: token.grants,
45934598
created_at: token.created_at,

src/auth.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub struct NewSession {
4040
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4141
pub struct StoredAgentToken {
4242
pub name: String,
43+
#[serde(default)]
44+
pub display_name: Option<String>,
4345
pub token_hash: String,
4446
#[serde(default)]
4547
pub owner: Option<UserName>,
@@ -49,14 +51,20 @@ pub struct StoredAgentToken {
4951

5052
#[derive(Debug, Clone)]
5153
pub struct NewAgentToken {
52-
pub name: String,
54+
pub display_name: String,
5355
pub owner: UserName,
5456
pub grants: Vec<ProjectGrant>,
5557
}
5658

5759
impl NewAgentToken {
60+
pub fn slug(&self) -> String {
61+
slugify_agent_name(&self.display_name)
62+
}
63+
5864
pub fn validate(&self) -> Result<()> {
59-
validate_agent_token_name(&self.name)?;
65+
validate_agent_display_name(&self.display_name)?;
66+
let slug = self.slug();
67+
validate_agent_token_name(&slug)?;
6068
if self.grants.is_empty() {
6169
return Err(LoreError::Validation(
6270
"agent token must grant at least one project permission".into(),
@@ -457,9 +465,10 @@ impl LocalAuthStore {
457465

458466
pub fn create_agent_token(&self, token: NewAgentToken) -> Result<CreatedAgentToken> {
459467
token.validate()?;
468+
let slug = token.slug();
460469
let mut tokens = self.load_agent_tokens()?;
461470
if tokens.iter().any(|existing| {
462-
existing.name == token.name
471+
existing.name == slug
463472
&& existing.owner.as_ref() == Some(&token.owner)
464473
}) {
465474
return Err(LoreError::Validation("agent already exists".into()));
@@ -469,7 +478,8 @@ impl LocalAuthStore {
469478
grants.sort_by(|a, b| a.project.cmp(&b.project));
470479
let raw_token = format!("lore_at_{}_{}", Uuid::new_v4(), Uuid::new_v4());
471480
let stored = StoredAgentToken {
472-
name: token.name,
481+
name: slug,
482+
display_name: Some(token.display_name),
473483
token_hash: hash_agent_token(&raw_token),
474484
owner: Some(token.owner),
475485
grants,
@@ -860,6 +870,51 @@ fn validate_password(password: &str) -> Result<()> {
860870
Ok(())
861871
}
862872

873+
fn validate_agent_display_name(name: &str) -> Result<()> {
874+
if name.is_empty() || name.len() > MAX_AGENT_TOKEN_NAME_LEN {
875+
return Err(LoreError::Validation(format!(
876+
"agent name must be 1..={MAX_AGENT_TOKEN_NAME_LEN} characters"
877+
)));
878+
}
879+
if !name
880+
.chars()
881+
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | ' '))
882+
{
883+
return Err(LoreError::Validation(
884+
"agent name must contain only letters, digits, spaces, '.', '_' or '-'".into(),
885+
));
886+
}
887+
Ok(())
888+
}
889+
890+
fn slugify_agent_name(display_name: &str) -> String {
891+
let slug: String = display_name
892+
.chars()
893+
.map(|ch| {
894+
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') {
895+
ch.to_ascii_lowercase()
896+
} else {
897+
'-'
898+
}
899+
})
900+
.collect();
901+
// Collapse consecutive hyphens
902+
let mut result = String::with_capacity(slug.len());
903+
let mut prev_hyphen = false;
904+
for ch in slug.chars() {
905+
if ch == '-' {
906+
if !prev_hyphen {
907+
result.push(ch);
908+
}
909+
prev_hyphen = true;
910+
} else {
911+
result.push(ch);
912+
prev_hyphen = false;
913+
}
914+
}
915+
result.trim_matches('-').to_string()
916+
}
917+
863918
fn validate_agent_token_name(name: &str) -> Result<()> {
864919
if name.is_empty() || name.len() > MAX_AGENT_TOKEN_NAME_LEN {
865920
return Err(LoreError::Validation(format!(
@@ -1084,7 +1139,7 @@ mod tests {
10841139

10851140
let created = auth
10861141
.create_agent_token(NewAgentToken {
1087-
name: "worker-alpha".into(),
1142+
display_name: "worker-alpha".into(),
10881143
owner: UserName::new("admin").unwrap(),
10891144
grants: vec![ProjectGrant {
10901145
project: ProjectName::new("alpha.docs").unwrap(),

src/ui.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub struct ProjectListEntry {
292292
#[derive(Debug, Clone, Serialize)]
293293
pub struct AgentTokenSummary {
294294
pub name: String,
295+
pub display_name: String,
295296
pub owner: Option<String>,
296297
pub grants: Vec<ProjectGrant>,
297298
pub created_at: time::OffsetDateTime,
@@ -1525,7 +1526,7 @@ pub fn render_agents_page(
15251526
</a>"#,
15261527
escape_attribute(&agent.name),
15271528
cls,
1528-
escape_text(&agent.name),
1529+
escape_text(&agent.display_name),
15291530
escape_text(&grant_label),
15301531
)
15311532
})
@@ -1659,7 +1660,7 @@ pub fn render_agents_page(
16591660

16601661
format!(
16611662
r##"<section class="panel" style="margin-top: var(--s-5);">
1662-
<div class="panel-header"><h2>{name}</h2><p>{owner}-{name}</p></div>
1663+
<div class="panel-header"><h2>{display_name}</h2><p>{owner}-{slug}</p></div>
16631664
16641665
<div class="panel-header"><h3>Configuration</h3></div>
16651666
<form method="post" action="/ui/agents/{name_attr}/grants" id="edit-grants-form">
@@ -1711,7 +1712,8 @@ pub fn render_agents_page(
17111712
</form>
17121713
</div>
17131714
</section>"##,
1714-
name = escape_text(&agent.name),
1715+
display_name = escape_text(&agent.display_name),
1716+
slug = escape_text(&agent.name),
17151717
owner = escape_text(username),
17161718
name_attr = escape_attribute(&agent.name),
17171719
csrf_token = escape_attribute(csrf_token),
@@ -3259,7 +3261,7 @@ fn render_user_card(user: &UiUserSummary, agents: &[AgentTokenSummary], csrf_tok
32593261
.join(", ");
32603262
format!(
32613263
r#"<li><span class="meta-code">{}</span> <span style="font-size:0.82rem; color:var(--fg-muted);">{}</span></li>"#,
3262-
escape_text(&agent.name),
3264+
escape_text(&agent.display_name),
32633265
escape_text(&grants),
32643266
)
32653267
})

0 commit comments

Comments
 (0)