Compare commits

...
4 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5 3b2cdc07b7 Release 0.3.21
Test / test (push) Failing after 29s
Publish release / publish (push) Successful in 1m42s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 17:10:49 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 2de936d995 Hero paragraph centers its measure box
max-width: 46ch without auto margins left-anchored the box inside the
centered column; the text centered in the wrong frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 17:10:44 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 9fca79b8ee Release 0.3.20
Test / test (push) Failing after 25s
Publish release / publish (push) Successful in 1m36s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 16:54:40 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 3d51aa1e6a Sign-in becomes optional: KANIDM_URL unset disables auth cleanly
A content-only instance (westra preview) has no review desk and no
Kanidm client; booting no longer demands one. Auth routes answer 503
'sign-in is not configured on this instance'; everything public
renders as usual.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 16:54:35 +02:00
4 changed files with 32 additions and 6 deletions
Generated
+1 -1
View File
@@ -2948,7 +2948,7 @@ dependencies = [
[[package]] [[package]]
name = "portal" name = "portal"
version = "0.3.19" version = "0.3.21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "portal" name = "portal"
version = "0.3.19" version = "0.3.21"
edition = "2021" edition = "2021"
[lib] [lib]
+26 -4
View File
@@ -33,6 +33,13 @@ type OidcClient = CoreClient<
>; >;
pub struct Oidc { pub struct Oidc {
/// `None` when `KANIDM_URL` is unset: a content-only instance with
/// sign-in disabled - auth routes answer 503, everything public
/// renders as usual.
inner: Option<OidcInner>,
}
struct OidcInner {
client: OidcClient, client: OidcClient,
http: openidconnect::reqwest::Client, http: openidconnect::reqwest::Client,
} }
@@ -45,7 +52,13 @@ const REDIRECT_KEY: &str = "oidc_post_login_redirect";
impl Oidc { impl Oidc {
/// Discovers the provider and builds the client from environment: /// Discovers the provider and builds the client from environment:
/// `KANIDM_URL`, `OAUTH2_CLIENT_ID`, `OAUTH2_CLIENT_SECRET`, `PUBLIC_URL`. /// `KANIDM_URL`, `OAUTH2_CLIENT_ID`, `OAUTH2_CLIENT_SECRET`, `PUBLIC_URL`.
/// With `KANIDM_URL` unset, sign-in is disabled instead of fatal -
/// the shape of a public content instance without a review desk.
pub async fn from_env() -> anyhow::Result<Self> { pub async fn from_env() -> anyhow::Result<Self> {
if std::env::var("KANIDM_URL").is_err() {
tracing::warn!("KANIDM_URL not set - sign-in disabled on this instance");
return Ok(Self { inner: None });
}
let kanidm_url = require_env("KANIDM_URL")?; let kanidm_url = require_env("KANIDM_URL")?;
let client_id = require_env("OAUTH2_CLIENT_ID")?; let client_id = require_env("OAUTH2_CLIENT_ID")?;
let client_secret = require_env("OAUTH2_CLIENT_SECRET")?; let client_secret = require_env("OAUTH2_CLIENT_SECRET")?;
@@ -75,7 +88,16 @@ impl Oidc {
) )
.set_redirect_uri(redirect); .set_redirect_uri(redirect);
Ok(Self { client, http }) Ok(Self {
inner: Some(OidcInner { client, http }),
})
}
fn configured(&self) -> Result<&OidcInner, HandlerError> {
self.inner.as_ref().ok_or((
StatusCode::SERVICE_UNAVAILABLE,
"sign-in is not configured on this instance".to_string(),
))
} }
} }
@@ -118,10 +140,10 @@ pub async fn login(
session.insert(REDIRECT_KEY, redirect).await.map_err(internal)?; session.insert(REDIRECT_KEY, redirect).await.map_err(internal)?;
} }
let oidc = state.oidc.configured()?;
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let (auth_url, csrf_state, nonce) = state let (auth_url, csrf_state, nonce) = oidc
.oidc
.client .client
.authorize_url( .authorize_url(
CoreAuthenticationFlow::AuthorizationCode, CoreAuthenticationFlow::AuthorizationCode,
@@ -182,7 +204,7 @@ pub async fn callback(
)); ));
} }
let oidc = &state.oidc; let oidc = state.oidc.configured()?;
let token_response = oidc let token_response = oidc
.client .client
.exchange_code(AuthorizationCode::new(params.code)) .exchange_code(AuthorizationCode::new(params.code))
+4
View File
@@ -193,6 +193,10 @@ main.not-found {
color: var(--ink-dim); color: var(--ink-dim);
font-size: 1.05rem; font-size: 1.05rem;
max-width: 46ch; max-width: 46ch;
/* The measure cap shrinks the box below the copy column; without
auto margins the box left-anchors and its centered text centers
in the wrong frame. */
margin-inline: auto;
} }
/* A content-shipped hero module (site.yaml hero.kind: module) draws /* A content-shipped hero module (site.yaml hero.kind: module) draws