headless_lms_server/domain/langs/
token.rs1use 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}