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
+32
-3
@@ -11,10 +11,10 @@ use axum::{
|
||||
response::Redirect,
|
||||
};
|
||||
use openidconnect::{
|
||||
core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata},
|
||||
core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata, CoreUserInfoClaims},
|
||||
AuthorizationCode, ClientId, ClientSecret, CsrfToken, EndpointMaybeSet, EndpointNotSet,
|
||||
EndpointSet, IssuerUrl, Nonce, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope,
|
||||
TokenResponse,
|
||||
OAuth2TokenResponse, TokenResponse,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tower_sessions::Session;
|
||||
@@ -50,6 +50,12 @@ const NONCE_KEY: &str = "oidc_nonce";
|
||||
const REDIRECT_KEY: &str = "oidc_post_login_redirect";
|
||||
|
||||
impl Oidc {
|
||||
/// Sign-in disabled: what a content-only instance has, and what a
|
||||
/// test builds an `AppState` with.
|
||||
pub fn disabled() -> Self {
|
||||
Self { inner: None }
|
||||
}
|
||||
|
||||
/// Discovers the provider and builds the client from environment:
|
||||
/// `KANIDM_URL`, `OAUTH2_CLIENT_ID`, `OAUTH2_CLIENT_SECRET`, `PUBLIC_URL`.
|
||||
/// With `KANIDM_URL` unset, sign-in is disabled instead of fatal -
|
||||
@@ -153,6 +159,9 @@ pub async fn login(
|
||||
.add_scope(Scope::new("openid".to_string()))
|
||||
.add_scope(Scope::new("profile".to_string()))
|
||||
.add_scope(Scope::new("email".to_string()))
|
||||
// A provider that verifies phone numbers (Vipps) gives one for
|
||||
// this scope; Kanidm ignores a scope it does not know.
|
||||
.add_scope(Scope::new("phoneNumber".to_string()))
|
||||
.set_pkce_challenge(pkce_challenge)
|
||||
.url();
|
||||
|
||||
@@ -242,13 +251,33 @@ pub async fn callback(
|
||||
let groups = extract_groups_claim(&id_token);
|
||||
|
||||
let email = claims.email().map(|e| e.as_str().trim().to_lowercase());
|
||||
let user = User {
|
||||
// The verified phone number lives at userinfo, not in the ID token,
|
||||
// for providers that give one (Vipps). A provider that has none, or
|
||||
// no userinfo endpoint, leaves it empty; nothing depends on it.
|
||||
let phone = match oidc.client.user_info(token_response.access_token().clone(), None) {
|
||||
Ok(request) => request
|
||||
.request_async(&oidc.http)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|info: CoreUserInfoClaims| info.phone_number().map(|p| p.as_str().to_string())),
|
||||
Err(_) => None,
|
||||
};
|
||||
let mut user = User {
|
||||
sub: claims.subject().as_str().to_string(),
|
||||
username,
|
||||
display_name,
|
||||
groups,
|
||||
phone,
|
||||
email,
|
||||
};
|
||||
// What they belong to, as portal keeps it: every invitation for
|
||||
// them becomes a membership now, and their active memberships join
|
||||
// whatever groups the provider itself asserted.
|
||||
for group in crate::memberships::on_signed_in(&state, &user).await {
|
||||
if !user.groups.contains(&group) {
|
||||
user.groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the session id on privilege change, then store the user.
|
||||
session.cycle_id().await.map_err(internal)?;
|
||||
|
||||
Reference in New Issue
Block a user