Files
portal/src/auth.rs
T
Bendik Aagaard LynghaugandClaude Fable 5.1 f047e21c4a
Test / test (pull_request) Successful in 58s
Memberships: groups kept by portal itself, a person by phone number first
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
2026-09-30 13:28:28 +02:00

39 lines
1.4 KiB
Rust

use leptos::prelude::*;
use serde::{Deserialize, Serialize};
/// The authenticated user, as established by the Kanidm OIDC flow and
/// stored in the server-side session. Ported from cnats' `auth.rs` -
/// same shape, same provider.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct User {
pub sub: String,
pub username: String,
pub display_name: String,
/// Kanidm group membership, from the `groups` OIDC claim (see
/// `oauth2 update-claim-map`). Fixed at login time - not re-checked
/// live, so a group change only takes effect on the next login.
#[serde(default)]
pub groups: Vec<String>,
/// The phone number the provider verified (`phone_number`, from
/// userinfo), when it gives one: what a membership is keyed by first.
#[serde(default)]
pub phone: Option<String>,
/// The address Kanidm holds for them (the `email` claim), so a page
/// can know it is talking to the person it names as responsible.
#[serde(default)]
pub email: Option<String>,
}
pub const SESSION_USER_KEY: &str = "user";
/// Returns the currently signed-in user, if any.
#[server(endpoint = "current_user")]
pub async fn current_user() -> Result<Option<User>, ServerFnError> {
let session: tower_sessions::Session = leptos_axum::extract().await?;
let user = session
.get::<User>(SESSION_USER_KEY)
.await
.map_err(|e| ServerFnError::new(e.to_string()))?;
Ok(user)
}