diff --git a/CHANGELOG.md b/CHANGELOG.md index ee330a4..b5d6848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ One line per change, grouped by the release that shipped it. Newest first. A release is a Cargo.toml bump plus a `v` tag; the content repos pin the tag they run (see the end of this file). +## Unreleased + +- Fixed: 0.5.7 asked every provider for the `phoneNumber` scope, and Kanidm denies a sign-in that asks for a scope the person does not hold, so nobody could sign in. The scope is asked for only with `OIDC_EXTRA_SCOPES=phoneNumber` (Vipps). 0.5.7 must not be deployed on a Kanidm site. + ## 0.5.7 (2026-09-30) - Memberships: groups kept by portal itself (`src/memberships.rs`, `docs/memberships.md`). 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; `removed` by a desk. A person is their phone number first (`+4791234567`, `PHONE_COUNTRY` for bare numbers), their address second, the provider's subject third, and one record per group and person however it was keyed. At sign-in the active memberships join the `groups` claim, so Cedar sees the same parents as before. `PEOPLE_BACKEND=memberships` switches a site; unset, a `KANIDM_API_TOKEN` means Kanidm and no token means memberships. `SEED_ADMIN_PHONE` seeds by number. The sign-in asks for the `phoneNumber` scope and reads `phone_number` from userinfo. diff --git a/docs/memberships.md b/docs/memberships.md index dace816..a619a02 100644 --- a/docs/memberships.md +++ b/docs/memberships.md @@ -76,9 +76,10 @@ memberships. With memberships: - The `groups` claim, if the provider sends one, still counts: a person's groups are the union. -The sign-in flow asks for the `phoneNumber` scope and reads -`phone_number` from userinfo; a provider without either gives nothing, -and matching falls back to the address. +With `OIDC_EXTRA_SCOPES=phoneNumber` the sign-in asks the provider for +the number and reads `phone_number` from userinfo. Set it for Vipps; +not for Kanidm, which denies a sign-in that asks for a scope the +person does not hold. Without it, matching falls back to the address. ## Asking who belongs diff --git a/docs/operations.md b/docs/operations.md index 6131a12..163ae47 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -72,6 +72,7 @@ values. | `PEOPLE_BACKEND` | `kanidm` or `memberships` (see [Memberships](memberships.md)); unset, Kanidm when `KANIDM_API_TOKEN` is set, memberships otherwise | | `KANIDM_API_TOKEN` | invites and `grants:` through Kanidm | | `PHONE_COUNTRY` | the calling code a bare phone number gets, `+47` unless set | +| `OIDC_EXTRA_SCOPES` | scopes to ask the provider for besides `openid profile email`: `phoneNumber` for Vipps. Not for Kanidm, which denies a sign-in that asks for a scope the person does not hold | | `SEED_ADMIN_EMAIL` or `SEED_ADMIN_PHONE`, `SEED_ADMIN_NAME`, `SEED_GROUP` | the first person in, invited once at start into the most privileged group (or `SEED_GROUP`) | | `RESPONSIBLE_GROUP` | the group every person a page names as `responsible` joins, at start and whenever the content changes, and leaves when no page names them any more; they get an account either way | | `PUBLIC_URL`, `SITE_NAME` | what the site calls itself in links and mail | diff --git a/src/server/oidc.rs b/src/server/oidc.rs index 27e04c0..7908bfe 100644 --- a/src/server/oidc.rs +++ b/src/server/oidc.rs @@ -149,7 +149,7 @@ pub async fn login( let oidc = state.oidc.configured()?; let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - let (auth_url, csrf_state, nonce) = oidc + let mut request = oidc .client .authorize_url( CoreAuthenticationFlow::AuthorizationCode, @@ -159,11 +159,14 @@ 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(); + .set_pkce_challenge(pkce_challenge); + // What else to ask the provider for: `phoneNumber` for one that + // verifies numbers (Vipps). Only when asked, because Kanidm denies + // a sign-in that requests a scope the person does not hold. + for scope in std::env::var("OIDC_EXTRA_SCOPES").unwrap_or_default().split_whitespace() { + request = request.add_scope(Scope::new(scope.to_string())); + } + let (auth_url, csrf_state, nonce) = request.url(); session .insert(PKCE_KEY, pkce_verifier.secret())