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 =
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}