arminius_server_client/handlers/
profile.rs1#![allow(
8 clippy::allow_attributes,
9 reason = "This source file contains enums which allow `clippy::missing_docs_in_private_items`. But an exception to this lint will mistakenly report that it's unfulfilled when used with expect, even though it is not."
10)]
11
12mod tests;
13
14use arminius_lib_component::{
15 Component, ServerState,
16 error::{InternalServerError, MatrixApiError, ResultExt as _},
17 ruma_requests::ArminiusResponse,
18};
19use arminius_lib_db::DefaultDb;
20use arminius_lib_db_user::UserQueries as _;
21use arminius_server_federation_output::clients::FederationNatsClient;
22use axum::{Router, extract::State, http::StatusCode, routing::get};
23use ruma::{
24 OwnedMxcUri,
25 api::{
26 client::profile::{get_avatar_url, get_display_name, get_profile},
27 error::ErrorKind,
28 },
29};
30use serde_json::json;
31
32use crate::client_request::UnauthenticatedClientRequest;
33
34#[allow(
35 missing_docs,
36 clippy::missing_docs_in_private_items,
37 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
38)]
39#[derive(Debug, thiserror::Error, MatrixApiError)]
40enum ProfileQueryError {
41 #[error(transparent)]
42 InternalServerError(#[from] InternalServerError),
43
44 #[error("User profile not found")]
46 #[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound)]
47 ProfileNotFound,
48}
49
50fn build_profile_response(
52 avatar_url: Option<OwnedMxcUri>,
53 display_name: Option<String>,
54) -> get_profile::v3::Response {
55 let mut response = get_profile::v3::Response::new();
56
57 if let Some(avatar_url) = avatar_url {
58 response.extend([("avatar_url".to_owned(), json!(avatar_url))]);
59 }
60
61 if let Some(display_name) = display_name {
62 response.extend([("displayname".to_owned(), json!(display_name))]);
63 }
64
65 response
66}
67
68async fn get_profile_handler(
73 mut state: State<ServerState>,
74 UnauthenticatedClientRequest(request): UnauthenticatedClientRequest<get_profile::v3::Request>,
75) -> Result<ArminiusResponse<get_profile::v3::Response>, ProfileQueryError> {
76 if request.user_id.server_name() == state.config.server_name {
77 let user = state
78 .database
79 .fetch_user(&request.user_id)
80 .await
81 .unrecoverable_error("Fetching user")?
82 .ok_or(ProfileQueryError::ProfileNotFound)?;
83
84 let avatar_url = user
85 .avatar_id
86 .as_ref()
87 .map(|id| crate::avatar_id_to_mxc(request.user_id.server_name(), id));
88
89 Ok(build_profile_response(avatar_url, user.display_name).into())
90 } else {
91 tracing::debug!("Got a foreign profile request. Making a federated profile request");
93 let client = FederationNatsClient::new(state.nats_connection.client.clone());
94 let federation_response = client
95 .get_federated_profile(request.user_id)
96 .await
97 .unrecoverable_error("Federation request failed")?
98 .unrecoverable_error("Federation request failed")?;
99
100 let avatar_url =
101 federation_response.get("avatar_url").and_then(|x| x.as_str().map(OwnedMxcUri::from));
102 let display_name =
103 federation_response.get("displayname").and_then(|x| x.as_str().map(ToOwned::to_owned));
104
105 Ok(build_profile_response(avatar_url, display_name).into())
106 }
107}
108
109async fn get_display_name_handler(
115 mut database: State<DefaultDb>,
116 UnauthenticatedClientRequest(request): UnauthenticatedClientRequest<
117 get_display_name::v3::Request,
118 >,
119) -> Result<ArminiusResponse<get_display_name::v3::Response>, ProfileQueryError> {
120 let user = database
121 .fetch_user(&request.user_id)
122 .await
123 .unrecoverable_error("Fetching user")?
124 .ok_or(ProfileQueryError::ProfileNotFound)?;
125
126 Ok(get_display_name::v3::Response::new(user.display_name).into())
127}
128
129async fn get_avatar_url_handler(
135 mut database: State<DefaultDb>,
136 UnauthenticatedClientRequest(request): UnauthenticatedClientRequest<
137 get_avatar_url::v3::Request,
138 >,
139) -> Result<ArminiusResponse<get_avatar_url::v3::Response>, ProfileQueryError> {
140 let user = database
141 .fetch_user(&request.user_id)
142 .await
143 .unrecoverable_error("Fetching user")?
144 .ok_or(ProfileQueryError::ProfileNotFound)?;
145
146 Ok(get_avatar_url::v3::Response::new(
147 user.avatar_id
148 .as_ref()
149 .map(|id| crate::avatar_id_to_mxc(request.user_id.server_name(), id)),
150 )
151 .into())
152}
153
154#[derive(Debug)]
156pub struct ProfileComponent;
157
158impl Component for ProfileComponent {
159 fn router() -> Router<ServerState> {
160 Router::new()
161 .route("/_matrix/client/v3/profile/{userId}", get(get_profile_handler))
162 .route("/_matrix/client/v3/profile/{userId}/displayname", get(get_display_name_handler))
163 .route("/_matrix/client/v3/profile/{userId}/avatar_url", get(get_avatar_url_handler))
164 }
165}