1
use std::sync::Arc;
2

            
3
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
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::{self, 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 user = sqlx::query_as!(
92
        User,
93
        "INSERT INTO users (user_name,email,user_password) VALUES ($1, $2, $3) 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",
94
        body.name.clone(),
95
        body.email.clone().to_ascii_lowercase(),
96
        hashed_password
97
    )
98
    .fetch_one(&mut *conn)
99
    .await
100
    .map_err(|e| {
101
        let msg = format!("{} {}", t!("Database error:"), e);
102
        log::error!("{msg}");
103
        let error_response = serde_json::json!({
104
            "status": "fail",
105
            "message": msg,
106
        });
107
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
108
    })?;
109

            
110
    let user_response = serde_json::json!({"status": "success","data": serde_json::json!({
111
        "user": filter_user_record(&user)
112
    })});
113

            
114
    Ok(Json(user_response))
115
}
116

            
117
pub async fn login_user_handler(
118
    State(data): State<Arc<AppState>>,
119
    Json(body): Json<LoginUserSchema>,
120
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
121
    let mut conn = get_connection().await.map_err(|e| {
122
        let msg = format!("{} {}", t!("Database error:"), e);
123
        log::error!("{msg}");
124
        let error_response = serde_json::json!({
125
            "status": "fail",
126
            "message": msg,
127
        });
128
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
129
    })?;
130

            
131
    let user = sqlx::query_as!(
132
        User,
133
        "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 email = $1",
134
        body.email.to_ascii_lowercase()
135
    )
136
    .fetch_optional(&mut *conn)
137
    .await
138
    .map_err(|e| {
139
        let msg = format!("{} {}", t!("Database error:"), e);
140
        log::error!("{msg}");
141
        let error_response = serde_json::json!({
142
            "status": "error",
143
            "message": msg,
144
        });
145
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
146
    })?
147
    .ok_or_else(|| {
148
        let msg = format!("{}", t!("Invalid email or password"));
149
        log::error!("{msg}");
150
        let error_response = serde_json::json!({
151
            "status": "fail",
152
            "message": msg,
153
        });
154
        (StatusCode::BAD_REQUEST, Json(error_response))
155
    })?;
156

            
157
    let is_valid = match PasswordHash::new(&user.password) {
158
        Ok(parsed_hash) => Argon2::default()
159
            .verify_password(body.password.as_bytes(), &parsed_hash)
160
            .is_ok_and(|()| true),
161
        Err(_) => false,
162
    };
163

            
164
    if !is_valid {
165
        let msg = format!("{}", t!("Invalid email or password"));
166
        log::error!("{msg}");
167
        let error_response = serde_json::json!({
168
            "status": "fail",
169
            "message": msg,
170
        });
171
        return Err((StatusCode::BAD_REQUEST, Json(error_response)));
172
    }
173

            
174
    let access_token_details = generate_token(
175
        user.id,
176
        data.conf.access_token_max_age,
177
        &data.conf.access_token_private_key,
178
    )?;
179
    let refresh_token_details = generate_token(
180
        user.id,
181
        data.conf.refresh_token_max_age,
182
        &data.conf.refresh_token_private_key,
183
    )?;
184

            
185
    save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
186
    save_token_data_to_redis(
187
        &data,
188
        &refresh_token_details,
189
        data.conf.refresh_token_max_age,
190
    )
191
    .await?;
192

            
193
    let access_cookie = Cookie::build((
194
        "access_token",
195
        access_token_details.token.clone().unwrap_or_default(),
196
    ))
197
    .path("/")
198
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
199
    .same_site(SameSite::Lax)
200
    .http_only(true);
201

            
202
    let refresh_cookie = Cookie::build((
203
        "refresh_token",
204
        refresh_token_details.token.unwrap_or_default(),
205
    ))
206
    .path("/")
207
    .max_age(time::Duration::minutes(
208
        data.conf.refresh_token_max_age * 60,
209
    ))
210
    .same_site(SameSite::Lax)
211
    .http_only(true);
212

            
213
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
214
        .path("/")
215
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
216
        .same_site(SameSite::Lax)
217
        .http_only(false);
218

            
219
    let mut response = Response::new(
220
        json!({"status": "success", "access_token": access_token_details.token.unwrap()})
221
            .to_string(),
222
    );
223
    let mut headers = HeaderMap::new();
224
    headers.append(
225
        header::SET_COOKIE,
226
        access_cookie.to_string().parse().unwrap(),
227
    );
228
    headers.append(
229
        header::SET_COOKIE,
230
        refresh_cookie.to_string().parse().unwrap(),
231
    );
232
    headers.append(
233
        header::SET_COOKIE,
234
        logged_in_cookie.to_string().parse().unwrap(),
235
    );
236
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
237

            
238
    response.headers_mut().extend(headers);
239
    Ok(response)
240
}
241

            
242
3
pub async fn refresh_access_token_handler(
243
3
    cookie_jar: CookieJar,
244
3
    State(data): State<Arc<AppState>>,
245
3
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
246
3
    let message = t!("could not refresh access token");
247

            
248
3
    let refresh_token = cookie_jar
249
3
        .get("refresh_token")
250
3
        .map(|cookie| cookie.value().to_string())
251
3
        .ok_or_else(|| {
252
2
            let error_response = serde_json::json!({
253
2
                "status": "fail",
254
2
                "message": message
255
            });
256
2
            (StatusCode::FORBIDDEN, Json(error_response))
257
2
        })?;
258

            
259
    let refresh_token_details =
260
1
        match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
261
            Ok(token_details) => token_details,
262
1
            Err(e) => {
263
1
                let error_response = serde_json::json!({
264
1
                    "status": "fail",
265
1
                    "message": format_args!("{:?}", e)
266
                });
267
1
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
268
            }
269
        };
270

            
271
    let mut redis_client = data
272
        .redis_client
273
        .get_multiplexed_async_connection()
274
        .await
275
        .map_err(|e| {
276
            let msg = format!("{} {}", t!("Redis error:"), e);
277
            log::error!("{msg}");
278
            let error_response = serde_json::json!({
279
                "status": "error",
280
                "message": msg,
281
            });
282
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
283
        })?;
284

            
285
    let redis_token_user_id = redis_client
286
        .get::<_, String>(refresh_token_details.token_uuid.to_string())
287
        .await
288
        .map_err(|_| {
289
            let msg = format!("{}", t!("Token is invalid or session has expired"));
290
            log::error!("{msg}");
291
            let error_response = serde_json::json!({
292
                "status": "error",
293
                "message": msg,
294
            });
295
            (StatusCode::UNAUTHORIZED, Json(error_response))
296
        })?;
297

            
298
    let user_id_uuid = uuid::Uuid::parse_str(&redis_token_user_id).map_err(|_| {
299
        let msg = format!("{}", t!("Token is invalid or session has expired"));
300
        log::error!("{msg}");
301
        let error_response = serde_json::json!({
302
            "status": "error",
303
            "message": msg,
304
        });
305
        (StatusCode::UNAUTHORIZED, Json(error_response))
306
    })?;
307

            
308
    let mut conn = get_connection().await.map_err(|e| {
309
        let msg = format!("{} {}", t!("Database error:"), e);
310
        log::error!("{msg}");
311
        let error_response = serde_json::json!({
312
            "status": "fail",
313
            "message": msg,
314
        });
315
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
316
    })?;
317

            
318
    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)
319
        .fetch_optional(&mut *conn)
320
        .await
321
        .map_err(|e| {
322
            let msg = format!("{} {}", t!("Error fetching user from database:"), e);
323
            log::error!("{msg}");
324
            let error_response = serde_json::json!({
325
                "status": "fail",
326
                "message": msg,
327
            });
328
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
329
        })?;
330

            
331
    let user = user.ok_or_else(|| {
332
        let msg = format!(
333
            "{}",
334
            t!("The user belonging to this token no longer exists")
335
        );
336
        log::error!("{msg}");
337
        let error_response = serde_json::json!({
338
            "status": "fail",
339
            "message": msg,
340
        });
341
        (StatusCode::UNAUTHORIZED, Json(error_response))
342
    })?;
343

            
344
    let access_token_details = generate_token(
345
        user.id,
346
        data.conf.access_token_max_age,
347
        &data.conf.access_token_private_key,
348
    )?;
349

            
350
    save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
351

            
352
    let access_cookie = Cookie::build((
353
        "access_token",
354
        access_token_details.token.clone().unwrap_or_default(),
355
    ))
356
    .path("/")
357
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
358
    .same_site(SameSite::Lax)
359
    .http_only(true);
360

            
361
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
362
        .path("/")
363
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
364
        .same_site(SameSite::Lax)
365
        .http_only(false);
366

            
367
    let mut response = Response::new(
368
        json!({"status": "success", "access_token": access_token_details.token.unwrap()})
369
            .to_string(),
370
    );
371
    let mut headers = HeaderMap::new();
372
    headers.append(
373
        header::SET_COOKIE,
374
        access_cookie.to_string().parse().unwrap(),
375
    );
376
    headers.append(
377
        header::SET_COOKIE,
378
        logged_in_cookie.to_string().parse().unwrap(),
379
    );
380

            
381
    response.headers_mut().extend(headers);
382
    Ok(response)
383
3
}
384

            
385
pub async fn logout_handler(
386
    cookie_jar: CookieJar,
387
    Extension(auth_guard): Extension<JWTAuthMiddleware>,
388
    State(data): State<Arc<AppState>>,
389
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
390
    let message = t!("Token is invalid or session has expired");
391

            
392
    let refresh_token = cookie_jar
393
        .get("refresh_token")
394
        .map(|cookie| cookie.value().to_string())
395
        .ok_or_else(|| {
396
            let error_response = serde_json::json!({
397
                "status": "fail",
398
                "message": message
399
            });
400
            (StatusCode::FORBIDDEN, Json(error_response))
401
        })?;
402

            
403
    let refresh_token_details =
404
        match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
405
            Ok(token_details) => token_details,
406
            Err(e) => {
407
                let error_response = serde_json::json!({
408
                    "status": "fail",
409
                    "message": format_args!("{:?}", e)
410
                });
411
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
412
            }
413
        };
414

            
415
    let mut redis_client = data
416
        .redis_client
417
        .get_multiplexed_async_connection()
418
        .await
419
        .map_err(|e| {
420
            let msg = format!("{} {}", t!("Redis error:"), e);
421
            log::error!("{msg}");
422
            let error_response = serde_json::json!({
423
                "status": "error",
424
                "message": msg,
425
            });
426
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
427
        })?;
428

            
429
    let _: bool = redis_client
430
        .del(&[
431
            refresh_token_details.token_uuid.to_string(),
432
            auth_guard.access_token_uuid.to_string(),
433
        ])
434
        .await
435
        .map_err(|e| {
436
            let error_response = serde_json::json!({
437
                "status": "error",
438
                "message": format_args!("{:?}", e)
439
            });
440
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
441
        })?;
442

            
443
    let access_cookie = Cookie::build(("access_token", ""))
444
        .path("/")
445
        .max_age(time::Duration::minutes(-1))
446
        .same_site(SameSite::Lax)
447
        .http_only(true);
448
    let refresh_cookie = Cookie::build(("refresh_token", ""))
449
        .path("/")
450
        .max_age(time::Duration::minutes(-1))
451
        .same_site(SameSite::Lax)
452
        .http_only(true);
453

            
454
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
455
        .path("/")
456
        .max_age(time::Duration::minutes(-1))
457
        .same_site(SameSite::Lax)
458
        .http_only(false);
459

            
460
    let mut headers = HeaderMap::new();
461
    headers.append(
462
        header::SET_COOKIE,
463
        access_cookie.to_string().parse().unwrap(),
464
    );
465
    headers.append(
466
        header::SET_COOKIE,
467
        refresh_cookie.to_string().parse().unwrap(),
468
    );
469
    headers.append(
470
        header::SET_COOKIE,
471
        logged_in_cookie.to_string().parse().unwrap(),
472
    );
473
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
474

            
475
    let mut response = Response::new(json!({"status": "success"}).to_string());
476
    response.headers_mut().extend(headers);
477
    Ok(response)
478
}
479

            
480
pub async fn get_me_handler(
481
    Extension(jwtauth): Extension<JWTAuthMiddleware>,
482
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
483
    let json_response = serde_json::json!({
484
        "status":  "success",
485
        "data": serde_json::json!({
486
            "user": filter_user_record(&jwtauth.user)
487
        })
488
    });
489

            
490
    Ok(Json(json_response))
491
}
492

            
493
2
pub async fn get_version() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
494
2
    if let Some(CmdResult::String(version)) = server::command::config::GetVersion::new()
495
2
        .run()
496
2
        .await
497
2
        .map_err(|_| {
498
            (
499
                StatusCode::INTERNAL_SERVER_ERROR,
500
                Json(json!("Can't get version")),
501
            )
502
        })?
503
2
        && let Some(CmdResult::String(build_date)) = server::command::config::GetBuildDate::new()
504
2
            .run()
505
2
            .await
506
2
            .map_err(|_| {
507
                (
508
                    StatusCode::INTERNAL_SERVER_ERROR,
509
                    Json(json!("Can't get version")),
510
                )
511
            })?
512
    {
513
2
        Ok(Response::new(format!(
514
2
            "<span class=\"version\">{version}</span><span class=\"build_date\" data-iso=\"{build_date}\"><script>
515
2
    const el = document.currentScript.parentElement;
516
2
    el.textContent = new Date(el.dataset.iso.trim()).toLocaleString();
517
2
  </script></span>"
518
2
        )))
519
    } else {
520
        Ok(Response::new("Unversioned".to_string()))
521
    }
522
2
}
523

            
524
1
pub async fn get_logout_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
525
1
    Ok(Response::new(
526
1
        "<a hx-get=\"/api/auth/logout\">Logout</a>".to_string(),
527
1
    ))
528
1
}
529

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

            
534
fn filter_user_record(user: &User) -> FilteredUser {
535
    FilteredUser {
536
        id: user.id.to_string(),
537
        email: user.email.clone(),
538
        name: user.name.clone(),
539
        photo: user.photo.clone(),
540
        role: user.role.clone(),
541
        verified: user.verified,
542
        createdAt: user.created_at.unwrap(),
543
        updatedAt: user.updated_at.unwrap(),
544
    }
545
}
546

            
547
fn generate_token(
548
    user_id: uuid::Uuid,
549
    max_age: i64,
550
    private_key: &str,
551
) -> Result<TokenDetails, (StatusCode, Json<serde_json::Value>)> {
552
    token::generate_jwt_token(user_id, max_age, private_key).map_err(|e| {
553
        let msg = format!("{} {}", t!("error generating token:"), e);
554
        log::error!("{msg}");
555
        let error_response = serde_json::json!({
556
            "status": "error",
557
            "message": msg,
558
        });
559
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
560
    })
561
}
562

            
563
async fn save_token_data_to_redis(
564
    data: &Arc<AppState>,
565
    token_details: &TokenDetails,
566
    max_age: i64,
567
) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
568
    let mut redis_client = data
569
        .redis_client
570
        .get_multiplexed_async_connection()
571
        .await
572
        .map_err(|e| {
573
            let msg = format!("{} {}", t!("Redis error:"), e);
574
            log::error!("{msg}");
575
            let error_response = serde_json::json!({
576
                "status": "error",
577
                "message": msg,
578
            });
579
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
580
        })?;
581
    let _: bool = redis_client
582
        .set_ex(
583
            token_details.token_uuid.to_string(),
584
            token_details.user_id.to_string(),
585
            (max_age * 60) as u64,
586
        )
587
        .await
588
        .map_err(|e| {
589
            let error_response = serde_json::json!({
590
                "status": "error",
591
                "message": format_args!("{}", e),
592
            });
593
            (StatusCode::UNPROCESSABLE_ENTITY, Json(error_response))
594
        })?;
595
    Ok(())
596
}