1pub(crate) mod account;
2pub(crate) mod auth_issuer;
3pub(crate) mod auth_metadata;
4pub(crate) mod authorize;
5pub(crate) mod complete;
6pub(crate) mod device;
7pub(crate) mod jwks;
8pub(crate) mod native;
9pub(crate) mod registration;
10pub(crate) mod revoke;
11pub(crate) mod token;
12pub(crate) mod userinfo;
13
14pub mod routes;
18
19use axum::{Json, body::Body, response::IntoResponse};
20use http::{Response, StatusCode};
21use log as _;
22use ruma::{OwnedUserId, UserId};
23use serde_json::json;
24use tuwunel_api::router::{ClientIp, RateLimitIp, State};
25pub(crate) use tuwunel_core::utils::url::{redirect_allowlisted, url_encode};
26use tuwunel_core::{Result, err, mod_ctor, mod_dtor};
27use tuwunel_service::{Services, users::LoginToken};
28use url::Url;
29
30pub(crate) use self::{
31 account::{
32 ACCOUNT_MANAGEMENT_ACTIONS_SUPPORTED, account_css_route, account_js_route,
33 get_account_callback_route, get_account_route, post_account_callback_route,
34 },
35 auth_issuer::auth_issuer_route,
36 auth_metadata::openid_configuration_route,
37 authorize::authorize_route,
38 complete::{complete_route, post_complete_route},
39 device::{
40 device_authorization_route, get_device_callback_route, get_device_route,
41 post_device_callback_route,
42 },
43 jwks::jwks_route,
44 native::{native_get_route, native_submit_route},
45 registration::registration_route,
46 revoke::revoke_route,
47 token::token_route,
48 userinfo::userinfo_route,
49};
50
51const OIDC_REQ_ID_LENGTH: usize = 32;
52
53fn oauth_error(status: StatusCode, error: &str, description: &str) -> Response<Body> {
54 let body = json!({
55 "error": error,
56 "error_description": description,
57 });
58
59 (status, Json(body)).into_response()
60}
61
62#[tracing::instrument(level = "debug", skip_all)]
63async fn require_account_usable(services: &Services, user_id: &UserId) -> Result {
64 services.users.deactivated_check(user_id).await?;
65 services.users.locked_check(user_id).await
66}
67
68async fn consume_login_token(services: &Services, token: Option<&str>) -> Result<OwnedUserId> {
69 let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
70
71 services
72 .users
73 .find_from_login_token(token)
74 .await
75 .map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
76}
77
78async fn consume_login_token_with_provider(
79 services: &Services,
80 token: Option<&str>,
81) -> Result<LoginToken> {
82 let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
83
84 services
85 .users
86 .find_login_token_with_provider(token)
87 .await
88 .map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
89}
90
91async fn peek_login_token(services: &Services, token: Option<&str>) -> Result<OwnedUserId> {
94 let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
95
96 services
97 .users
98 .peek_login_token(token)
99 .await
100 .map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
101}
102
103async fn peek_login_token_with_provider(
104 services: &Services,
105 token: Option<&str>,
106) -> Result<LoginToken> {
107 let token = token.ok_or_else(|| err!(Request(Forbidden("Missing login token"))))?;
108
109 services
110 .users
111 .peek_login_token_with_provider(token)
112 .await
113 .map_err(|_| err!(Request(Forbidden("Invalid or expired login token"))))
114}
115
116fn authorization_sso_url(issuer: &str, idp_id: &str, req_id: &str) -> Result<Url> {
122 let base = issuer.trim_end_matches('/');
123 let complete = format!("{base}/_tuwunel/oidc/_complete");
124 let callback = Url::parse_with_params(&complete, [("oidc_req_id", req_id)])
125 .map_err(|_| err!(error!("Failed to build complete URL")))?;
126
127 sso_redirect_url(base, idp_id, &callback)
128}
129
130fn sso_redirect_url(base: &str, idp_id: &str, callback: &Url) -> Result<Url> {
131 let idp_id_enc = url_encode(idp_id);
132 let mut sso_url =
133 Url::parse(&format!("{base}/_matrix/client/v3/login/sso/redirect/{idp_id_enc}"))
134 .map_err(|_| err!(error!("Failed to build SSO URL")))?;
135
136 sso_url
137 .query_pairs_mut()
138 .append_pair("redirectUrl", callback.as_str());
139
140 Ok(sso_url)
141}
142
143mod_ctor! {}
144mod_dtor! {}