headless_lms_server/domain/langs/
token.rs

1use crate::{
2    domain::authorization::{self, get_or_create_user_from_tmc_mooc_fi_response},
3    prelude::*,
4};
5use actix_web::{FromRequest, http::header};
6use futures_util::{FutureExt, future::LocalBoxFuture};
7use headless_lms_utils::{cache::Cache, tmc::TmcClient};
8use models::users::User;
9use secrecy::{ExposeSecret, SecretString};
10use std::ops::{Deref, DerefMut};
11use std::time::Duration;
12
13#[derive(Debug, Clone)]
14pub struct UserFromTMCAccessToken(User);
15
16impl Deref for UserFromTMCAccessToken {
17    type Target = User;
18    fn deref(&self) -> &Self::Target {
19        &self.0
20    }
21}
22
23impl DerefMut for UserFromTMCAccessToken {
24    fn deref_mut(&mut self) -> &mut Self::Target {
25        &mut self.0
26    }
27}
28
29impl FromRequest for UserFromTMCAccessToken {
30    type Error = ControllerError;
31    type Future = LocalBoxFuture<'static, Result<Self, ControllerError>>;
32
33    fn from_request(req: &HttpRequest, _payload: &mut actix_http::Payload) -> Self::Future {
34        let pool = req
35            .app_data::<web::Data<PgPool>>()
36            .expect("Missing database pool")
37            .clone();
38        let app_conf = req
39            .app_data::<web::Data<ApplicationConfiguration>>()
40            .expect("Missing application configuration")
41            .clone();
42        let cache = req
43            .app_data::<web::Data<Cache>>()
44            .expect("Missing cache")
45            .clone();
46
47        let auth_header = req
48            .headers()
49            .get(header::AUTHORIZATION)
50            .map(|hv| String::from_utf8_lossy(hv.as_bytes()))
51            .and_then(|h| h.strip_prefix("Bearer ").map(str::to_string))
52            .map(|o| SecretString::new(o.into()));
53
54        let tmc_client: web::Data<TmcClient> = req
55            .app_data::<web::Data<TmcClient>>()
56            .expect("Missing TMC client")
57            .clone();
58        async move {
59            let Some(token) = auth_header else {
60                return Err(ControllerError::new(
61                    ControllerErrorType::Unauthorized,
62                    "Missing bearer token".to_string(),
63                    None,
64                ));
65            };
66            let mut conn = pool.acquire().await?;
67
68            let user = if app_conf.test_mode {
69                warn!("Using test credentials. Normal accounts won't work.");
70                authorization::authenticate_test_token(&mut conn, &token, &app_conf)
71                    .await
72                    .map_err(|err| {
73                        ControllerError::new(
74                            ControllerErrorType::Unauthorized,
75                            "Could not find user".to_string(),
76                            Some(err),
77                        )
78                    })?
79            } else {
80                match load_user(&cache, &token).await {
81                    Some(user) => user,
82                    None => {
83                        let tmc_user = tmc_client
84                            .get_user_from_tmc_mooc_fi_by_tmc_access_token(&token.clone())
85                            .await?;
86
87                        debug!(
88                            "Creating or fetching user with TMC id {} and mooc.fi UUID {}",
89                            tmc_user.id,
90                            tmc_user
91                                .courses_mooc_fi_user_id
92                                .map(|uuid| uuid.to_string())
93                                .unwrap_or_else(|| "None (will generate new UUID)".to_string())
94                        );
95                        let user = get_or_create_user_from_tmc_mooc_fi_response(
96                            &mut conn, tmc_user, &token,
97                        )
98                        .await?;
99                        info!(
100                            "Successfully got user details from mooc.fi for user {}",
101                            user.id
102                        );
103                        cache_user(&cache, &token, &user).await;
104                        user
105                    }
106                }
107            };
108
109            Ok(Self(user))
110        }
111        .boxed_local()
112    }
113}
114
115#[derive(Debug, Deserialize)]
116#[allow(unused)]
117struct TmcUser {
118    id: i32,
119    username: String,
120    email: String,
121    administrator: bool,
122}
123
124fn token_to_cache_key(token: &SecretString) -> String {
125    let mut hasher = blake3::Hasher::new();
126    hasher.update(token.expose_secret().as_bytes());
127    format!("user:{}", hasher.finalize().to_hex())
128}
129
130pub async fn cache_user(cache: &Cache, token: &SecretString, user: &User) {
131    cache
132        .cache_json(
133            token_to_cache_key(token),
134            user,
135            Duration::from_secs(60 * 60),
136        )
137        .await;
138}
139
140pub async fn load_user(cache: &Cache, token: &SecretString) -> Option<User> {
141    cache.get_json(token_to_cache_key(token)).await
142}