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
63async 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}