Memberships: groups kept by portal itself, a person by phone number first
Test / test (pull_request) Successful in 58s
Test / test (pull_request) Successful in 58s
A membership is a case in the built-in portal_memberships bucket: invited by a desk, a grants, the seed or the responsible pass; active from the person's first sign-in, when the provider's subject, phone number or address matches the record; removed by a desk, and invited again after that. One record per group and person however it was keyed, and at sign-in every record that matched learns what the provider verified. The active memberships join the groups claim, so Cedar sees the same parents as before. PEOPLE_BACKEND picks Kanidm or memberships; unset, a KANIDM_API_TOKEN means Kanidm. The NATS access service looks a person's groups up when a question names them by sub, phone or email and lists none. The sign-in asks for the phoneNumber scope and reads it from userinfo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b9761cec63
commit
f047e21c4a
+21
-10
@@ -2652,6 +2652,16 @@ pub async fn my_suggestions(question_id: String) -> Result<Vec<MySuggestion>, Se
|
||||
Ok(mine)
|
||||
}
|
||||
|
||||
/// Who a principal is, for a record: the username, or the service.
|
||||
#[cfg(feature = "ssr")]
|
||||
fn principal_name(principal: &crate::access::Principal) -> String {
|
||||
match principal {
|
||||
crate::access::Principal::User { sub, .. } => sub.clone(),
|
||||
crate::access::Principal::Submitter { email, .. } => email.clone(),
|
||||
crate::access::Principal::Service { name } => name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[server(endpoint = "submit_answer")]
|
||||
pub async fn submit_answer(
|
||||
question_id: String,
|
||||
@@ -2743,21 +2753,22 @@ pub async fn submit_answer(
|
||||
if !may.allow {
|
||||
return Err(ServerFnError::new("not authorized to invite into this group"));
|
||||
}
|
||||
let kanidm = crate::people::Kanidm::from_env()
|
||||
.ok_or_else(|| ServerFnError::new("invites are not configured on this instance (KANIDM_API_TOKEN)"))?;
|
||||
let field = |name: &str| responses.get(name).and_then(|v| v.as_str()).map(str::trim).filter(|s| !s.is_empty()).map(String::from);
|
||||
let (name, email) = match (field("name"), field("email")) {
|
||||
(Some(n), Some(e)) => (n, e),
|
||||
_ => return Err(ServerFnError::new("an invite needs a name and an email")),
|
||||
let people = crate::people::People::from_env().ok_or_else(|| ServerFnError::new(crate::people::People::NOT_CONFIGURED))?;
|
||||
let Some((name, who)) = crate::people::who_for(&responses) else {
|
||||
return Err(ServerFnError::new("an invite needs a name, and a phone number or an email"));
|
||||
};
|
||||
let invited = kanidm
|
||||
.invite(&name, &email, &invite.group)
|
||||
let invited = people
|
||||
.invite(&state, &name, &who, &invite.group, &principal_name(&principal))
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(format!("invite failed: {e}")))?;
|
||||
if let Some(map) = responses.as_object_mut() {
|
||||
map.insert("invited".to_string(), serde_json::to_value(&invited).unwrap_or_default());
|
||||
}
|
||||
record_as.get_or_insert_with(|| crate::people::INVITES_BUCKET.to_string());
|
||||
// Through Kanidm the invite is its own case, with a link that
|
||||
// runs out; a membership is the case already.
|
||||
if people.is_kanidm() {
|
||||
record_as.get_or_insert_with(|| crate::people::INVITES_BUCKET.to_string());
|
||||
}
|
||||
if let Ok(bytes) = serde_json::to_vec(&invited) {
|
||||
let _ = state.nats.publish(crate::people::INVITED_SUBJECT, bytes.into()).await;
|
||||
}
|
||||
@@ -2908,7 +2919,7 @@ mod responsible_tests {
|
||||
use crate::content::Responsible;
|
||||
|
||||
fn user(email: Option<&str>, groups: &[&str]) -> User {
|
||||
User { sub: "s".into(), username: "kari".into(), display_name: "Kari".into(), groups: groups.iter().map(|g| g.to_string()).collect(), email: email.map(String::from) }
|
||||
User { sub: "s".into(), username: "kari".into(), display_name: "Kari".into(), groups: groups.iter().map(|g| g.to_string()).collect(), phone: None, email: email.map(String::from) }
|
||||
}
|
||||
|
||||
fn named(contact: &str, group: Option<&str>) -> Responsible {
|
||||
|
||||
Reference in New Issue
Block a user