1
use std::sync::Arc;
2

            
3
use argon2::{Argon2, PasswordHasher};
4
use axum::{
5
    Extension, Json,
6
    extract::State,
7
    http::{HeaderMap, Response, StatusCode, header},
8
    response::IntoResponse,
9
};
10
use axum_extra::extract::{
11
    CookieJar,
12
    cookie::{Cookie, SameSite},
13
};
14
use serde_json::json;
15

            
16
use crate::{
17
    AppState,
18
    jwt_auth::JWTAuthMiddleware,
19
    model::{LoginUserSchema, RegisterUserSchema, User},
20
    response::FilteredUser,
21
    token::TokenDetails,
22
};
23
use rust_i18n::t;
24

            
25
use redis::AsyncCommands;
26
use server::{command::CmdResult, db::get_connection};
27

            
28
pub async fn register_user_handler(
29
    State(_data): State<Arc<AppState>>,
30
    Json(body): Json<RegisterUserSchema>,
31
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
32
    let mut conn = get_connection().await.map_err(|e| {
33
        let msg = format!("{} {}", t!("Database error:"), e);
34
        log::error!("{msg}");
35
        let error_response = serde_json::json!({
36
            "status": "fail",
37
            "message": msg,
38
        });
39
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
40
    })?;
41

            
42
    let user_exists: Option<bool> =
43
        sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)")
44
            .bind(body.email.clone().to_ascii_lowercase())
45
            .fetch_one(&mut *conn)
46
            .await
47
            .map_err(|e| {
48
                let msg = format!("{} {}", t!("Database error:"), e);
49
                log::error!("{msg}");
50
                let error_response = serde_json::json!({
51
                    "status": "fail",
52
                    "message": msg,
53
                });
54
                (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
55
            })?;
56

            
57
    if let Some(exists) = user_exists
58
        && exists
59
    {
60
        let msg = format!("{}", t!("User with that email already exists"));
61
        log::error!("{msg}");
62
        let error_response = serde_json::json!({
63
            "status": "fail",
64
            "message": msg,
65
        });
66
        return Err((StatusCode::CONFLICT, Json(error_response)));
67
    }
68

            
69
    let hashed_password = Argon2::default()
70
        .hash_password(body.password.as_bytes())
71
        .map_err(|e| {
72
            let msg = format!("{} {}", t!("Error while hashing password:"), e);
73
            log::error!("{msg}");
74
            let error_response = serde_json::json!({
75
                "status": "fail",
76
                "message": msg,
77
            });
78
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
79
        })
80
        .map(|hash| hash.to_string())?;
81
    let mut conn = get_connection().await.map_err(|e| {
82
        let msg = format!("{} {}", t!("Database error:"), e);
83
        log::error!("{msg}");
84
        let error_response = serde_json::json!({
85
            "status": "fail",
86
            "message": msg,
87
        });
88
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
89
    })?;
90

            
91
    let internal_error = |e: &dyn std::fmt::Display| {
92
        let msg = format!("{} {}", t!("Database error:"), e);
93
        log::error!("{msg}");
94
        let error_response = serde_json::json!({ "status": "fail", "message": msg });
95
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
96
    };
97

            
98
    let user_id = uuid::Uuid::new_v4();
99
    // Generate the user's signing keypair first (off the request thread); the
100
    // private half goes into their per-user DB, the public half into the global
101
    // users row.
102
    let keypair = server::auth_keys::generate()
103
        .await
104
        .map_err(|e| internal_error(&e))?;
105
    let db_name = server::provision::create_user_database(user_id)
106
        .await
107
        .map_err(|e| internal_error(&e))?;
108

            
109
    if let Err(e) =
110
        server::provision::store_user_private_key(&db_name, &keypair.private_pem_b64).await
111
    {
112
        // The DB exists but key storage failed; drop the orphan so a retry
113
        // starts clean.
114
        if let Err(drop_err) = server::provision::drop_user_database(user_id).await {
115
            log::error!(
116
                "{} {}",
117
                t!("Failed to clean up provisioned database:"),
118
                drop_err
119
            );
120
        }
121
        return Err(internal_error(&e));
122
    }
123

            
124
    let insert = sqlx::query_as!(
125
        User,
126
        "INSERT INTO users (id,user_name,email,user_password,db_name,jwt_public_key) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, user_name as name, email, user_password as password, user_role as role, photo, verified, db_name as database, created_at, updated_at",
127
        user_id,
128
        body.name.clone(),
129
        body.email.clone().to_ascii_lowercase(),
130
        hashed_password,
131
        db_name,
132
        keypair.public_pem_b64
133
    )
134
    .fetch_one(&mut *conn)
135
    .await;
136

            
137
    let user = match insert {
138
        Ok(user) => user,
139
        Err(e) => {
140
            // The per-user database was already created; the user row failed
141
            // (e.g. a concurrent registration won the UNIQUE(email) race).
142
            // Drop the orphan so failed registrations don't leak databases.
143
            if let Err(drop_err) = server::provision::drop_user_database(user_id).await {
144
                log::error!(
145
                    "{} {}",
146
                    t!("Failed to clean up provisioned database after registration error:"),
147
                    drop_err
148
                );
149
            }
150
            let msg = format!("{} {}", t!("Database error:"), e);
151
            log::error!("{msg}");
152
            let error_response = serde_json::json!({
153
                "status": "fail",
154
                "message": msg,
155
            });
156
            return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)));
157
        }
158
    };
159

            
160
    let user_response = serde_json::json!({"status": "success","data": serde_json::json!({
161
        "user": filter_user_record(&user)
162
    })});
163

            
164
    Ok(Json(user_response))
165
}
166

            
167
struct LoginCookies {
168
    access: Cookie<'static>,
169
    refresh: Cookie<'static>,
170
    logged_in: Cookie<'static>,
171
    access_token: String,
172
}
173

            
174
async fn authenticate_user(
175
    data: &Arc<AppState>,
176
    email: &str,
177
    password: &str,
178
) -> Result<LoginCookies, (StatusCode, Json<serde_json::Value>)> {
179
    let verified = server::command::user::verify_user_password(email, password)
180
        .await
181
        .map_err(|e| {
182
            let msg = format!("{} {}", t!("Database error:"), e);
183
            log::error!("{msg}");
184
            let error_response = serde_json::json!({
185
                "status": "error",
186
                "message": msg,
187
            });
188
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
189
        })?
190
        .ok_or_else(|| {
191
            let msg = format!("{}", t!("Invalid email or password"));
192
            log::error!("{msg}");
193
            let error_response = serde_json::json!({
194
                "status": "fail",
195
                "message": msg,
196
            });
197
            (StatusCode::BAD_REQUEST, Json(error_response))
198
        })?;
199

            
200
    let access_token_details = mint_token(
201
        verified.user_id,
202
        data.conf.access_token_max_age,
203
        crate::token::TokenType::Access,
204
    )
205
    .await?;
206
    let refresh_token_details = mint_token(
207
        verified.user_id,
208
        data.conf.refresh_token_max_age,
209
        crate::token::TokenType::Refresh,
210
    )
211
    .await?;
212

            
213
    save_token_data_to_redis(data, &access_token_details, data.conf.access_token_max_age).await?;
214
    save_token_data_to_redis(
215
        data,
216
        &refresh_token_details,
217
        data.conf.refresh_token_max_age,
218
    )
219
    .await?;
220

            
221
    let access_cookie = Cookie::build((
222
        "access_token",
223
        access_token_details.token.clone().unwrap_or_default(),
224
    ))
225
    .path("/")
226
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
227
    .same_site(SameSite::Lax)
228
    .secure(crate::auth_keys::secure_cookies())
229
    .http_only(true)
230
    .build();
231

            
232
    let refresh_cookie = Cookie::build((
233
        "refresh_token",
234
        refresh_token_details.token.unwrap_or_default(),
235
    ))
236
    .path("/")
237
    .max_age(time::Duration::minutes(
238
        data.conf.refresh_token_max_age * 60,
239
    ))
240
    .same_site(SameSite::Lax)
241
    .secure(crate::auth_keys::secure_cookies())
242
    .http_only(true)
243
    .build();
244

            
245
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
246
        .path("/")
247
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
248
        .same_site(SameSite::Lax)
249
        .http_only(false)
250
        .build();
251

            
252
    Ok(LoginCookies {
253
        access: access_cookie,
254
        refresh: refresh_cookie,
255
        logged_in: logged_in_cookie,
256
        access_token: access_token_details.token.unwrap_or_default(),
257
    })
258
}
259

            
260
fn build_cookie_headers(cookies: &LoginCookies) -> HeaderMap {
261
    let mut headers = HeaderMap::new();
262
    headers.append(
263
        header::SET_COOKIE,
264
        cookies.access.to_string().parse().unwrap(),
265
    );
266
    headers.append(
267
        header::SET_COOKIE,
268
        cookies.refresh.to_string().parse().unwrap(),
269
    );
270
    headers.append(
271
        header::SET_COOKIE,
272
        cookies.logged_in.to_string().parse().unwrap(),
273
    );
274
    headers
275
}
276

            
277
pub async fn login_user_handler(
278
    State(data): State<Arc<AppState>>,
279
    Json(body): Json<LoginUserSchema>,
280
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
281
    let cookies = authenticate_user(&data, &body.email, &body.password).await?;
282

            
283
    let mut response = Response::new(
284
        json!({"status": "success", "access_token": cookies.access_token}).to_string(),
285
    );
286
    let mut headers = build_cookie_headers(&cookies);
287
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
288
    response.headers_mut().extend(headers);
289
    Ok(response)
290
}
291

            
292
pub async fn login_form_handler(
293
    State(data): State<Arc<AppState>>,
294
    axum::Form(body): axum::Form<LoginUserSchema>,
295
) -> impl IntoResponse {
296
    match authenticate_user(&data, &body.email, &body.password).await {
297
        Ok(cookies) => {
298
            let mut headers = build_cookie_headers(&cookies);
299
            headers.insert(header::LOCATION, "/".parse().unwrap());
300
            (StatusCode::SEE_OTHER, headers, "").into_response()
301
        }
302
        Err((status, json_err)) => {
303
            let msg = json_err
304
                .get("message")
305
                .and_then(|v| v.as_str())
306
                .unwrap_or("Login failed");
307
            (status, msg.to_string()).into_response()
308
        }
309
    }
310
}
311

            
312
3
pub async fn refresh_access_token_handler(
313
3
    cookie_jar: CookieJar,
314
3
    State(data): State<Arc<AppState>>,
315
3
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
316
3
    let message = t!("could not refresh access token");
317

            
318
3
    let refresh_token = cookie_jar
319
3
        .get("refresh_token")
320
3
        .map(|cookie| cookie.value().to_string())
321
3
        .ok_or_else(|| {
322
2
            let error_response = serde_json::json!({
323
2
                "status": "fail",
324
2
                "message": message
325
            });
326
2
            (StatusCode::FORBIDDEN, Json(error_response))
327
2
        })?;
328

            
329
    let refresh_token_details =
330
1
        match crate::auth_keys::verify(&refresh_token, crate::token::TokenType::Refresh).await {
331
            Some(token_details) => token_details,
332
            None => {
333
1
                let error_response = serde_json::json!({
334
1
                    "status": "fail",
335
1
                    "message": t!("Token is invalid or session has expired")
336
                });
337
1
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
338
            }
339
        };
340

            
341
    let mut redis_client = data
342
        .redis_client
343
        .get_multiplexed_async_connection()
344
        .await
345
        .map_err(|e| {
346
            let msg = format!("{} {}", t!("Redis error:"), e);
347
            log::error!("{msg}");
348
            let error_response = serde_json::json!({
349
                "status": "error",
350
                "message": msg,
351
            });
352
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
353
        })?;
354

            
355
    // Atomically consume the presented refresh session (GETDEL) so this refresh
356
    // token is one-time-use: a concurrent replay finds it gone. The session is
357
    // re-registered under a fresh uuid below.
358
    let redis_token_user_id = redis_client
359
        .get_del::<_, Option<String>>(refresh_token_details.token_uuid.to_string())
360
        .await
361
        .map_err(|e| {
362
            let msg = format!("{} {}", t!("Redis error:"), e);
363
            log::error!("{msg}");
364
            let error_response = serde_json::json!({
365
                "status": "error",
366
                "message": msg,
367
            });
368
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
369
        })?
370
        .ok_or_else(|| {
371
            let msg = format!("{}", t!("Token is invalid or session has expired"));
372
            log::error!("{msg}");
373
            let error_response = serde_json::json!({
374
                "status": "error",
375
                "message": msg,
376
            });
377
            (StatusCode::UNAUTHORIZED, Json(error_response))
378
        })?;
379

            
380
    let user_id_uuid = uuid::Uuid::parse_str(&redis_token_user_id).map_err(|_| {
381
        let msg = format!("{}", t!("Token is invalid or session has expired"));
382
        log::error!("{msg}");
383
        let error_response = serde_json::json!({
384
            "status": "error",
385
            "message": msg,
386
        });
387
        (StatusCode::UNAUTHORIZED, Json(error_response))
388
    })?;
389

            
390
    // Bind the verified JWT subject to the Redis session owner (see jwt_auth).
391
    if user_id_uuid != refresh_token_details.user_id {
392
        let error_response = serde_json::json!({
393
            "status": "fail",
394
            "message": t!("Token is invalid or session has expired")
395
        });
396
        return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
397
    }
398

            
399
    let mut conn = get_connection().await.map_err(|e| {
400
        let msg = format!("{} {}", t!("Database error:"), e);
401
        log::error!("{msg}");
402
        let error_response = serde_json::json!({
403
            "status": "fail",
404
            "message": msg,
405
        });
406
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
407
    })?;
408

            
409
    let user = sqlx::query_as!(User, "SELECT id, user_name as name, email, user_password as password, user_role as role, photo, verified, db_name as database, created_at, updated_at FROM users WHERE id = $1", user_id_uuid)
410
        .fetch_optional(&mut *conn)
411
        .await
412
        .map_err(|e| {
413
            let msg = format!("{} {}", t!("Error fetching user from database:"), e);
414
            log::error!("{msg}");
415
            let error_response = serde_json::json!({
416
                "status": "fail",
417
                "message": msg,
418
            });
419
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
420
        })?;
421

            
422
    let user = user.ok_or_else(|| {
423
        let msg = format!(
424
            "{}",
425
            t!("The user belonging to this token no longer exists")
426
        );
427
        log::error!("{msg}");
428
        let error_response = serde_json::json!({
429
            "status": "fail",
430
            "message": msg,
431
        });
432
        (StatusCode::UNAUTHORIZED, Json(error_response))
433
    })?;
434

            
435
    let access_token_details = mint_token(
436
        user.id,
437
        data.conf.access_token_max_age,
438
        crate::token::TokenType::Access,
439
    )
440
    .await?;
441

            
442
    save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
443

            
444
    // Rotate the refresh token too: the old one was consumed (GETDEL) above, so
445
    // mint a fresh refresh token and register it. This keeps each refresh token
446
    // one-time-use on this endpoint as well.
447
    let refresh_token_details = mint_token(
448
        user.id,
449
        data.conf.refresh_token_max_age,
450
        crate::token::TokenType::Refresh,
451
    )
452
    .await?;
453

            
454
    save_token_data_to_redis(
455
        &data,
456
        &refresh_token_details,
457
        data.conf.refresh_token_max_age,
458
    )
459
    .await?;
460

            
461
    let access_cookie = Cookie::build((
462
        "access_token",
463
        access_token_details.token.clone().unwrap_or_default(),
464
    ))
465
    .path("/")
466
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
467
    .same_site(SameSite::Lax)
468
    .secure(crate::auth_keys::secure_cookies())
469
    .http_only(true);
470

            
471
    let refresh_cookie = Cookie::build((
472
        "refresh_token",
473
        refresh_token_details.token.unwrap_or_default(),
474
    ))
475
    .path("/")
476
    .max_age(time::Duration::minutes(
477
        data.conf.refresh_token_max_age * 60,
478
    ))
479
    .same_site(SameSite::Lax)
480
    .secure(crate::auth_keys::secure_cookies())
481
    .http_only(true);
482

            
483
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
484
        .path("/")
485
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
486
        .same_site(SameSite::Lax)
487
        .http_only(false);
488

            
489
    let mut response = Response::new(
490
        json!({"status": "success", "access_token": access_token_details.token.unwrap()})
491
            .to_string(),
492
    );
493
    let mut headers = HeaderMap::new();
494
    headers.append(
495
        header::SET_COOKIE,
496
        access_cookie.to_string().parse().unwrap(),
497
    );
498
    headers.append(
499
        header::SET_COOKIE,
500
        refresh_cookie.to_string().parse().unwrap(),
501
    );
502
    headers.append(
503
        header::SET_COOKIE,
504
        logged_in_cookie.to_string().parse().unwrap(),
505
    );
506

            
507
    response.headers_mut().extend(headers);
508
    Ok(response)
509
3
}
510

            
511
pub async fn logout_handler(
512
    cookie_jar: CookieJar,
513
    Extension(auth_guard): Extension<JWTAuthMiddleware>,
514
    State(data): State<Arc<AppState>>,
515
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
516
    let message = t!("Token is invalid or session has expired");
517

            
518
    let refresh_token = cookie_jar
519
        .get("refresh_token")
520
        .map(|cookie| cookie.value().to_string())
521
        .ok_or_else(|| {
522
            let error_response = serde_json::json!({
523
                "status": "fail",
524
                "message": message
525
            });
526
            (StatusCode::FORBIDDEN, Json(error_response))
527
        })?;
528

            
529
    let refresh_token_details =
530
        match crate::auth_keys::verify(&refresh_token, crate::token::TokenType::Refresh).await {
531
            Some(token_details) => token_details,
532
            None => {
533
                let error_response = serde_json::json!({
534
                    "status": "fail",
535
                    "message": t!("Token is invalid or session has expired")
536
                });
537
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
538
            }
539
        };
540

            
541
    let mut redis_client = data
542
        .redis_client
543
        .get_multiplexed_async_connection()
544
        .await
545
        .map_err(|e| {
546
            let msg = format!("{} {}", t!("Redis error:"), e);
547
            log::error!("{msg}");
548
            let error_response = serde_json::json!({
549
                "status": "error",
550
                "message": msg,
551
            });
552
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
553
        })?;
554

            
555
    // Bind the refresh token to the authenticated principal: the access token
556
    // (auth_guard) and the refresh token must belong to the same user, so a
557
    // client can't present user A's access token with user B's refresh token to
558
    // invalidate B's session.
559
    if refresh_token_details.user_id != auth_guard.user.id {
560
        let error_response = serde_json::json!({
561
            "status": "fail",
562
            "message": t!("Token is invalid or session has expired")
563
        });
564
        return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
565
    }
566

            
567
    let _: bool = redis_client
568
        .del(&[
569
            refresh_token_details.token_uuid.to_string(),
570
            auth_guard.access_token_uuid.to_string(),
571
        ])
572
        .await
573
        .map_err(|e| {
574
            let error_response = serde_json::json!({
575
                "status": "error",
576
                "message": format_args!("{:?}", e)
577
            });
578
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
579
        })?;
580

            
581
    let access_cookie = Cookie::build(("access_token", ""))
582
        .path("/")
583
        .max_age(time::Duration::minutes(-1))
584
        .same_site(SameSite::Lax)
585
        .secure(crate::auth_keys::secure_cookies())
586
        .http_only(true);
587
    let refresh_cookie = Cookie::build(("refresh_token", ""))
588
        .path("/")
589
        .max_age(time::Duration::minutes(-1))
590
        .same_site(SameSite::Lax)
591
        .secure(crate::auth_keys::secure_cookies())
592
        .http_only(true);
593

            
594
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
595
        .path("/")
596
        .max_age(time::Duration::minutes(-1))
597
        .same_site(SameSite::Lax)
598
        .http_only(false);
599

            
600
    let mut headers = HeaderMap::new();
601
    headers.append(
602
        header::SET_COOKIE,
603
        access_cookie.to_string().parse().unwrap(),
604
    );
605
    headers.append(
606
        header::SET_COOKIE,
607
        refresh_cookie.to_string().parse().unwrap(),
608
    );
609
    headers.append(
610
        header::SET_COOKIE,
611
        logged_in_cookie.to_string().parse().unwrap(),
612
    );
613
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
614

            
615
    let mut response = Response::new(json!({"status": "success"}).to_string());
616
    response.headers_mut().extend(headers);
617
    Ok(response)
618
}
619

            
620
pub async fn get_me_handler(
621
    Extension(jwtauth): Extension<JWTAuthMiddleware>,
622
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
623
    let json_response = serde_json::json!({
624
        "status":  "success",
625
        "data": serde_json::json!({
626
            "user": filter_user_record(&jwtauth.user)
627
        })
628
    });
629

            
630
    Ok(Json(json_response))
631
}
632

            
633
2
pub async fn get_version() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
634
2
    if let Some(CmdResult::String(version)) = server::command::config::GetVersion::new()
635
2
        .run()
636
2
        .await
637
2
        .map_err(|_| {
638
            (
639
                StatusCode::INTERNAL_SERVER_ERROR,
640
                Json(json!("Can't get version")),
641
            )
642
        })?
643
2
        && let Some(CmdResult::String(build_date)) = server::command::config::GetBuildDate::new()
644
2
            .run()
645
2
            .await
646
2
            .map_err(|_| {
647
                (
648
                    StatusCode::INTERNAL_SERVER_ERROR,
649
                    Json(json!("Can't get version")),
650
                )
651
            })?
652
    {
653
2
        Ok(Response::new(format!(
654
2
            "<span class=\"version\">{version}</span><span class=\"build_date\" data-iso=\"{build_date}\"><script>
655
2
    const el = document.currentScript.parentElement;
656
2
    el.textContent = new Date(el.dataset.iso.trim()).toLocaleString();
657
2
  </script></span>"
658
2
        )))
659
    } else {
660
        Ok(Response::new("Unversioned".to_string()))
661
    }
662
2
}
663

            
664
1
pub async fn get_logout_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
665
1
    Ok(Response::new(
666
1
        "<a hx-get=\"/api/auth/logout\">Logout</a>".to_string(),
667
1
    ))
668
1
}
669

            
670
pub async fn get_home_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
671
    Ok(Response::new("<a href='/'>Back to main</a>".to_string()))
672
}
673

            
674
fn filter_user_record(user: &User) -> FilteredUser {
675
    FilteredUser {
676
        id: user.id.to_string(),
677
        email: user.email.clone(),
678
        name: user.name.clone(),
679
        photo: user.photo.clone(),
680
        role: user.role.clone(),
681
        verified: user.verified,
682
        createdAt: user.created_at.unwrap(),
683
        updatedAt: user.updated_at.unwrap(),
684
    }
685
}
686

            
687
async fn mint_token(
688
    user_id: uuid::Uuid,
689
    max_age: i64,
690
    token_type: crate::token::TokenType,
691
) -> Result<TokenDetails, (StatusCode, Json<serde_json::Value>)> {
692
    crate::auth_keys::mint(user_id, max_age, token_type)
693
        .await
694
        .map_err(|e| {
695
            let msg = format!("{} {}", t!("error generating token:"), e);
696
            log::error!("{msg}");
697
            let error_response = serde_json::json!({
698
                "status": "error",
699
                "message": msg,
700
            });
701
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
702
        })
703
}
704

            
705
async fn save_token_data_to_redis(
706
    data: &Arc<AppState>,
707
    token_details: &TokenDetails,
708
    max_age: i64,
709
) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
710
    let mut redis_client = data
711
        .redis_client
712
        .get_multiplexed_async_connection()
713
        .await
714
        .map_err(|e| {
715
            let msg = format!("{} {}", t!("Redis error:"), e);
716
            log::error!("{msg}");
717
            let error_response = serde_json::json!({
718
                "status": "error",
719
                "message": msg,
720
            });
721
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
722
        })?;
723
    let _: bool = redis_client
724
        .set_ex(
725
            token_details.token_uuid.to_string(),
726
            token_details.user_id.to_string(),
727
            (max_age * 60) as u64,
728
        )
729
        .await
730
        .map_err(|e| {
731
            let error_response = serde_json::json!({
732
                "status": "error",
733
                "message": format_args!("{}", e),
734
            });
735
            (StatusCode::UNPROCESSABLE_ENTITY, Json(error_response))
736
        })?;
737
    Ok(())
738
}