tuwunel_api/oidc/
userinfo.rs1use axum::{
2 Json, RequestPartsExt, body,
3 body::Body,
4 extract::{Request, State},
5 http::Method,
6 response::IntoResponse,
7};
8use axum_extra::{
9 TypedHeader,
10 headers::{Authorization, authorization::Bearer},
11};
12use futures::future::join;
13use http::{HeaderValue, Response, StatusCode, header};
14use serde::Deserialize;
15use serde_json::json;
16use tuwunel_core::{Err, Result, err, utils::TryFutureExtExt};
17use tuwunel_service::Services;
18
19use super::oauth_error;
20
21#[derive(Deserialize)]
22struct AccessTokenForm {
23 access_token: Option<String>,
24}
25
26pub(crate) async fn userinfo_route(
27 State(services): State<crate::State>,
28 request: Request,
29) -> Response<Body> {
30 userinfo_inner(&services, request)
31 .await
32 .unwrap_or_else(|e| {
33 let status = e.status_code();
34 let msg = e.sanitized_message();
35 let mut resp = oauth_error(status, "invalid_token", &msg);
36
37 if status == StatusCode::UNAUTHORIZED {
39 resp.headers_mut().insert(
40 header::WWW_AUTHENTICATE,
41 HeaderValue::from_static(r#"Bearer realm="Matrix", error="invalid_token""#),
42 );
43 }
44
45 resp
46 })
47}
48
49async fn userinfo_inner(services: &Services, request: Request) -> Result<Response<Body>> {
50 let (mut parts, body) = request.into_parts();
51
52 let bearer: Option<TypedHeader<Authorization<Bearer>>> =
54 parts.extract().await.unwrap_or(None);
55
56 let token = if let Some(TypedHeader(Authorization(b))) = bearer {
57 b.token().to_owned()
58 } else if parts.method == Method::POST {
59 let bytes = body::to_bytes(body, 8192)
61 .await
62 .map_err(|_| err!(Request(BadJson("Failed to read request body"))))?;
63
64 serde_html_form::from_bytes::<AccessTokenForm>(&bytes)
65 .ok()
66 .and_then(|f| f.access_token)
67 .ok_or_else(|| {
68 tuwunel_core::err!(Request(MissingToken("No access token provided")))
69 })?
70 } else {
71 return Err!(Request(MissingToken("No access token provided")));
72 };
73
74 let Ok((user_id, device_id, _expires)) = services.users.find_from_token(&token).await else {
75 return Err!(Request(Unauthorized("Invalid access token")));
76 };
77
78 if !services
82 .users
83 .is_oidc_device(&user_id, &device_id)
84 .await
85 {
86 return Err!(Request(Unauthorized("Token was not issued through OIDC")));
87 }
88
89 let avatar_url = services.users.avatar_url(&user_id).ok();
90
91 let displayname = services.users.displayname(&user_id).ok();
92
93 let (avatar_url, displayname) = join(avatar_url, displayname).await;
94
95 let response = json!({
96 "sub": user_id.to_string(),
97 "name": displayname,
98 "picture": avatar_url,
99 });
100
101 Ok(Json(response).into_response())
102}