Skip to main content

tuwunel_api/oidc/
mod.rs

1pub(super) mod account;
2pub(super) mod auth_issuer;
3pub(super) mod auth_metadata;
4pub(super) mod authorize;
5pub(super) mod complete;
6pub(super) mod device;
7pub(super) mod jwks;
8pub(super) mod native;
9pub(super) mod registration;
10pub(super) mod revoke;
11pub(super) mod token;
12pub(super) mod userinfo;
13
14use std::fmt::Write;
15
16use axum::{Json, body::Body, response::IntoResponse};
17use http::{Response, StatusCode};
18use ruma::OwnedUserId;
19use serde_json::json;
20use tuwunel_core::{Result, err};
21use tuwunel_service::Services;
22use url::Url;
23
24pub(super) use self::{
25	account::*, auth_issuer::*, auth_metadata::*, authorize::*, complete::*, device::*, jwks::*,
26	native::*, registration::*, revoke::*, token::*, userinfo::*,
27};
28
29const OIDC_REQ_ID_LENGTH: usize = 32;
30
31pub(crate) fn url_encode(s: &str) -> String {
32	s.bytes()
33		.fold(String::with_capacity(s.len()), |mut out, b| {
34			if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
35				out.push(b.into());
36			} else {
37				write!(&mut out, "%{b:02X}").ok();
38			}
39
40			out
41		})
42}
43
44fn oauth_error(status: StatusCode, error: &str, description: &str) -> Response<Body> {
45	let body = json!({
46		"error": error,
47		"error_description": description,
48	});
49
50	(status, Json(body)).into_response()
51}
52
53async fn consume_login_token(services: &Services, token: Option<&str>) -> Result<OwnedUserId> {
54	let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
55
56	services
57		.users
58		.find_from_login_token(token)
59		.await
60		.map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
61}
62
63/// Verify a login token without consuming it; it is consumed later when the
64/// confirmation form is submitted.
65async fn peek_login_token(services: &Services, token: Option<&str>) -> Result<OwnedUserId> {
66	let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
67
68	services
69		.users
70		.peek_login_token(token)
71		.await
72		.map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
73}
74
75fn sso_redirect_url(base: &str, idp_id: &str, callback: &Url) -> Result<Url> {
76	let idp_id_enc = url_encode(idp_id);
77	let mut sso_url =
78		Url::parse(&format!("{base}/_matrix/client/v3/login/sso/redirect/{idp_id_enc}"))
79			.map_err(|_| err!(error!("Failed to build SSO URL")))?;
80
81	sso_url
82		.query_pairs_mut()
83		.append_pair("redirectUrl", callback.as_str());
84
85	Ok(sso_url)
86}