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 =
96                            get_or_create_user_from_tmc_mooc_fi_response(&mut conn, tmc_user)
97                                .await?;
98                        info!(
99                            "Successfully got user details from mooc.fi for user {}",
100                            user.id
101                        );
102                        cache_user(&cache, &token, &user).await;
103                        user
104                    }
105                }
106            };
107
108            Ok(Self(user))
109        }
110        .boxed_local()
111    }
112}
113
114#[derive(Debug, Deserialize)]
115#[allow(unused)]
116struct TmcUser {
117    id: i32,
118    username: String,
119    email: String,
120    administrator: bool,
121}
122
123fn token_to_cache_key(token: &SecretString) -> String {
124    let mut hasher = blake3::Hasher::new();
125    hasher.update(token.expose_secret().as_bytes());
126    format!("user:{}", hasher.finalize().to_hex())
127}
128
129pub async fn cache_user(cache: &Cache, token: &SecretString, user: &User) {
130    cache
131        .cache_json(
132            token_to_cache_key(token),
133            user,
134            Duration::from_secs(60 * 60),
135        )
136        .await;
137}
138
139pub async fn load_user(cache: &Cache, token: &SecretString) -> Option<User> {
140    cache.get_json(token_to_cache_key(token)).await
141}