arminius_lib_authentication/
lib.rs1pub mod test_utils;
8mod tests;
9
10use std::collections::HashSet;
11
12use arminius_lib_component::{
13 ArminiusConfig, ServerState,
14 error::{InternalServerError, MatrixApiError, ResultExt as _},
15};
16use arminius_lib_db_authentication::AuthenticationQueries as _;
17use arminius_lib_reqwest::setup_insecure_reqwest_client;
18use axum::{
19 extract::FromRequestParts,
20 http::{StatusCode, request::Parts},
21};
22use jsonwebtoken::{DecodingKey, jwk};
23use ruma::{
24 OwnedDeviceId, OwnedUserId, UserId,
25 api::error::{ErrorKind, UnknownTokenErrorData},
26 exports::serde::{Deserialize, Serialize},
27};
28use serde::de::DeserializeOwned;
29use url::Url;
30
31const OIDC_WELL_KNOWN_REQUEST_FAILED: &str = "OIDC well known request failed";
33const OIDC_WELL_KNOWN_RESPONSE_ERROR: &str = "OIDC well known response error";
35const OIDC_JWKS_REQUEST_FAILED: &str = "OIDC jwks request failed";
37const OIDC_JWKS_RESPONSE_ERROR: &str = "OIDC jwks response error";
39const CANNOT_PROCESS_JWK_ALGORITHM: &str = "Cannot process JWK algorithm";
41const CANNOT_PROCESS_JWK: &str = "Cannot process JWK";
43
44#[derive(Serialize, Deserialize, Debug, Clone)]
46pub struct SessionInfo {
47 #[serde(rename = "sub")]
49 pub user_id: OwnedUserId,
50
51 pub device_id: OwnedDeviceId,
53}
54
55#[derive(Serialize, Deserialize, Debug, Clone)]
57struct Claims {
58 #[serde(rename = "iat")]
62 issued_at: i64,
63
64 #[serde(rename = "sub")]
67 subject: String,
68
69 #[serde(rename = "sid")]
72 session_id: String,
73}
74
75#[derive(MatrixApiError, thiserror::Error, Debug)]
77pub enum AuthError {
78 #[error("Token header is missing")]
80 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
81 MissingHeader,
82
83 #[error("Token header is not valid UTF-8")]
85 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
86 WrongEncoding,
87
88 #[error("No bearer token found")]
90 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
91 UnknownToken,
92
93 #[error("Decoding token failed")]
95 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
96 DecodeError,
97
98 #[error("Session is unknown")]
100 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
101 UnknownSession,
102
103 #[error(transparent)]
105 InternalServerError(#[from] InternalServerError),
106
107 #[error(transparent)]
109 JWKError(#[from] JWKError),
110}
111
112#[derive(MatrixApiError, thiserror::Error, Debug)]
114pub enum JWKError {
115 #[error(transparent)]
117 InternalServerError(#[from] InternalServerError),
118
119 #[error("User is not authorized")]
121 #[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::UnknownToken(UnknownTokenErrorData::new()))]
122 Unauthorized,
123}
124
125#[derive(Debug, Clone)]
128pub struct AuthenticatedClaims(pub SessionInfo);
129
130fn json_convert<I: Serialize, O: DeserializeOwned>(input: I) -> Result<O, serde_json::Error> {
132 serde_json::from_value(serde_json::to_value(input)?)
133}
134
135async fn fetch_jwk_set(
137 config: &ArminiusConfig,
138 oidc_well_known: &Url,
139) -> Result<jwk::JwkSet, InternalServerError> {
140 #[derive(serde::Deserialize)]
141 struct AuthMetadata {
142 jwks_uri: Url,
146 }
147
148 let client = setup_insecure_reqwest_client(config)
151 .unrecoverable_error("Could not set up reqwest client. Invalid proxy config?")?;
152 let auth_metadata_req = client.get(oidc_well_known.clone());
153 let auth_metadata_resp =
154 auth_metadata_req.send().await.unrecoverable_error(OIDC_WELL_KNOWN_REQUEST_FAILED)?;
155 let auth_metadata: AuthMetadata =
156 auth_metadata_resp.json().await.unrecoverable_error(OIDC_WELL_KNOWN_RESPONSE_ERROR)?;
157 let jwks_req = client.get(auth_metadata.jwks_uri);
158 let jwks_resp = jwks_req.send().await.unrecoverable_error(OIDC_JWKS_REQUEST_FAILED)?;
159
160 jwks_resp.json().await.unrecoverable_error(OIDC_JWKS_RESPONSE_ERROR)
161}
162
163async fn get_jwk(config: &ArminiusConfig, key_id: Option<String>) -> Result<jwk::Jwk, JWKError> {
166 let keys = if let Some(oidc_well_known) = &config.oidc_well_known {
167 fetch_jwk_set(config, oidc_well_known).await?
168 } else {
169 jwk::JwkSet { keys: vec![] }
170 }
171 .keys
172 .into_iter();
173
174 #[cfg(any(test, feature = "test_utils"))]
175 let keys = keys.chain(Some(test_utils::test_jwk()));
176
177 let mut keys = keys;
178
179 keys.find(|x| x.common.key_id == key_id).ok_or(JWKError::Unauthorized)
180}
181
182impl FromRequestParts<ServerState> for AuthenticatedClaims {
183 type Rejection = AuthError;
184
185 async fn from_request_parts(parts: &mut Parts, state: &ServerState) -> Result<Self, AuthError> {
187 let authorization_header =
188 parts.headers.get("Authorization").ok_or(AuthError::MissingHeader)?;
189 let authorization_header =
190 authorization_header.to_str().map_err(|_| AuthError::WrongEncoding)?;
191 let token = authorization_header.strip_prefix("Bearer ").ok_or(AuthError::UnknownToken)?;
192 let jwt_header = jsonwebtoken::decode_header(token).map_err(|_| AuthError::UnknownToken)?;
193 let jwk = get_jwk(&state.config, jwt_header.kid).await?;
194
195 let mut validation = jsonwebtoken::Validation::new(
196 json_convert(jwk.common.key_algorithm)
197 .unrecoverable_error(CANNOT_PROCESS_JWK_ALGORITHM)?,
198 );
199 validation.required_spec_claims = HashSet::new();
200 validation.validate_aud = false;
201
202 let decoding_key = DecodingKey::from_jwk(&jwk).unrecoverable_error(CANNOT_PROCESS_JWK)?;
203
204 let jwt = jsonwebtoken::decode::<Claims>(token, &decoding_key, &validation)
205 .map_err(|_| AuthError::UnknownToken)?;
206 let Claims { issued_at: iat, subject: sub, session_id: sid } = jwt.claims;
207 let db_session_info = state
208 .database
209 .clone()
210 .fetch_session_info(&sub, &sid, iat)
211 .await
212 .unrecoverable_error("Could not fetch session info from database")?
213 .ok_or(AuthError::UnknownSession)?;
214
215 Ok(AuthenticatedClaims(SessionInfo {
216 user_id: db_session_info
217 .user_id
218 .parse()
219 .unrecoverable_error("Failed to parse user ID")?,
220 device_id: db_session_info.device_id.into(),
221 }))
222 }
223}
224
225#[must_use]
227pub fn check_user_is_admin(_: &UserId) -> bool {
228 true
229}