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::{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
struct LoginCookies {
118
    access: Cookie<'static>,
119
    refresh: Cookie<'static>,
120
    logged_in: Cookie<'static>,
121
    access_token: String,
122
}
123

            
124
async fn authenticate_user(
125
    data: &Arc<AppState>,
126
    email: &str,
127
    password: &str,
128
) -> Result<LoginCookies, (StatusCode, Json<serde_json::Value>)> {
129
    let verified = server::command::user::verify_user_password(email, password)
130
        .await
131
        .map_err(|e| {
132
            let msg = format!("{} {}", t!("Database error:"), e);
133
            log::error!("{msg}");
134
            let error_response = serde_json::json!({
135
                "status": "error",
136
                "message": msg,
137
            });
138
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
139
        })?
140
        .ok_or_else(|| {
141
            let msg = format!("{}", t!("Invalid email or password"));
142
            log::error!("{msg}");
143
            let error_response = serde_json::json!({
144
                "status": "fail",
145
                "message": msg,
146
            });
147
            (StatusCode::BAD_REQUEST, Json(error_response))
148
        })?;
149

            
150
    let access_token_details = generate_token(
151
        verified.user_id,
152
        data.conf.access_token_max_age,
153
        &data.conf.access_token_private_key,
154
    )?;
155
    let refresh_token_details = generate_token(
156
        verified.user_id,
157
        data.conf.refresh_token_max_age,
158
        &data.conf.refresh_token_private_key,
159
    )?;
160

            
161
    save_token_data_to_redis(data, &access_token_details, data.conf.access_token_max_age).await?;
162
    save_token_data_to_redis(
163
        data,
164
        &refresh_token_details,
165
        data.conf.refresh_token_max_age,
166
    )
167
    .await?;
168

            
169
    let access_cookie = Cookie::build((
170
        "access_token",
171
        access_token_details.token.clone().unwrap_or_default(),
172
    ))
173
    .path("/")
174
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
175
    .same_site(SameSite::Lax)
176
    .http_only(true)
177
    .build();
178

            
179
    let refresh_cookie = Cookie::build((
180
        "refresh_token",
181
        refresh_token_details.token.unwrap_or_default(),
182
    ))
183
    .path("/")
184
    .max_age(time::Duration::minutes(
185
        data.conf.refresh_token_max_age * 60,
186
    ))
187
    .same_site(SameSite::Lax)
188
    .http_only(true)
189
    .build();
190

            
191
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
192
        .path("/")
193
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
194
        .same_site(SameSite::Lax)
195
        .http_only(false)
196
        .build();
197

            
198
    Ok(LoginCookies {
199
        access: access_cookie,
200
        refresh: refresh_cookie,
201
        logged_in: logged_in_cookie,
202
        access_token: access_token_details.token.unwrap_or_default(),
203
    })
204
}
205

            
206
fn build_cookie_headers(cookies: &LoginCookies) -> HeaderMap {
207
    let mut headers = HeaderMap::new();
208
    headers.append(
209
        header::SET_COOKIE,
210
        cookies.access.to_string().parse().unwrap(),
211
    );
212
    headers.append(
213
        header::SET_COOKIE,
214
        cookies.refresh.to_string().parse().unwrap(),
215
    );
216
    headers.append(
217
        header::SET_COOKIE,
218
        cookies.logged_in.to_string().parse().unwrap(),
219
    );
220
    headers
221
}
222

            
223
pub async fn login_user_handler(
224
    State(data): State<Arc<AppState>>,
225
    Json(body): Json<LoginUserSchema>,
226
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
227
    let cookies = authenticate_user(&data, &body.email, &body.password).await?;
228

            
229
    let mut response = Response::new(
230
        json!({"status": "success", "access_token": cookies.access_token}).to_string(),
231
    );
232
    let mut headers = build_cookie_headers(&cookies);
233
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
234
    response.headers_mut().extend(headers);
235
    Ok(response)
236
}
237

            
238
pub async fn login_form_handler(
239
    State(data): State<Arc<AppState>>,
240
    axum::Form(body): axum::Form<LoginUserSchema>,
241
) -> impl IntoResponse {
242
    match authenticate_user(&data, &body.email, &body.password).await {
243
        Ok(cookies) => {
244
            let mut headers = build_cookie_headers(&cookies);
245
            headers.insert(header::LOCATION, "/".parse().unwrap());
246
            (StatusCode::SEE_OTHER, headers, "").into_response()
247
        }
248
        Err((status, json_err)) => {
249
            let msg = json_err
250
                .get("message")
251
                .and_then(|v| v.as_str())
252
                .unwrap_or("Login failed");
253
            (status, msg.to_string()).into_response()
254
        }
255
    }
256
}
257

            
258
3
pub async fn refresh_access_token_handler(
259
3
    cookie_jar: CookieJar,
260
3
    State(data): State<Arc<AppState>>,
261
3
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
262
3
    let message = t!("could not refresh access token");
263

            
264
3
    let refresh_token = cookie_jar
265
3
        .get("refresh_token")
266
3
        .map(|cookie| cookie.value().to_string())
267
3
        .ok_or_else(|| {
268
2
            let error_response = serde_json::json!({
269
2
                "status": "fail",
270
2
                "message": message
271
            });
272
2
            (StatusCode::FORBIDDEN, Json(error_response))
273
2
        })?;
274

            
275
    let refresh_token_details =
276
1
        match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
277
            Ok(token_details) => token_details,
278
1
            Err(e) => {
279
1
                let error_response = serde_json::json!({
280
1
                    "status": "fail",
281
1
                    "message": format_args!("{:?}", e)
282
                });
283
1
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
284
            }
285
        };
286

            
287
    let mut redis_client = data
288
        .redis_client
289
        .get_multiplexed_async_connection()
290
        .await
291
        .map_err(|e| {
292
            let msg = format!("{} {}", t!("Redis error:"), e);
293
            log::error!("{msg}");
294
            let error_response = serde_json::json!({
295
                "status": "error",
296
                "message": msg,
297
            });
298
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
299
        })?;
300

            
301
    let redis_token_user_id = redis_client
302
        .get::<_, String>(refresh_token_details.token_uuid.to_string())
303
        .await
304
        .map_err(|_| {
305
            let msg = format!("{}", t!("Token is invalid or session has expired"));
306
            log::error!("{msg}");
307
            let error_response = serde_json::json!({
308
                "status": "error",
309
                "message": msg,
310
            });
311
            (StatusCode::UNAUTHORIZED, Json(error_response))
312
        })?;
313

            
314
    let user_id_uuid = uuid::Uuid::parse_str(&redis_token_user_id).map_err(|_| {
315
        let msg = format!("{}", t!("Token is invalid or session has expired"));
316
        log::error!("{msg}");
317
        let error_response = serde_json::json!({
318
            "status": "error",
319
            "message": msg,
320
        });
321
        (StatusCode::UNAUTHORIZED, Json(error_response))
322
    })?;
323

            
324
    let mut conn = get_connection().await.map_err(|e| {
325
        let msg = format!("{} {}", t!("Database error:"), e);
326
        log::error!("{msg}");
327
        let error_response = serde_json::json!({
328
            "status": "fail",
329
            "message": msg,
330
        });
331
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
332
    })?;
333

            
334
    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)
335
        .fetch_optional(&mut *conn)
336
        .await
337
        .map_err(|e| {
338
            let msg = format!("{} {}", t!("Error fetching user from database:"), e);
339
            log::error!("{msg}");
340
            let error_response = serde_json::json!({
341
                "status": "fail",
342
                "message": msg,
343
            });
344
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
345
        })?;
346

            
347
    let user = user.ok_or_else(|| {
348
        let msg = format!(
349
            "{}",
350
            t!("The user belonging to this token no longer exists")
351
        );
352
        log::error!("{msg}");
353
        let error_response = serde_json::json!({
354
            "status": "fail",
355
            "message": msg,
356
        });
357
        (StatusCode::UNAUTHORIZED, Json(error_response))
358
    })?;
359

            
360
    let access_token_details = generate_token(
361
        user.id,
362
        data.conf.access_token_max_age,
363
        &data.conf.access_token_private_key,
364
    )?;
365

            
366
    save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
367

            
368
    let access_cookie = Cookie::build((
369
        "access_token",
370
        access_token_details.token.clone().unwrap_or_default(),
371
    ))
372
    .path("/")
373
    .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
374
    .same_site(SameSite::Lax)
375
    .http_only(true);
376

            
377
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
378
        .path("/")
379
        .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
380
        .same_site(SameSite::Lax)
381
        .http_only(false);
382

            
383
    let mut response = Response::new(
384
        json!({"status": "success", "access_token": access_token_details.token.unwrap()})
385
            .to_string(),
386
    );
387
    let mut headers = HeaderMap::new();
388
    headers.append(
389
        header::SET_COOKIE,
390
        access_cookie.to_string().parse().unwrap(),
391
    );
392
    headers.append(
393
        header::SET_COOKIE,
394
        logged_in_cookie.to_string().parse().unwrap(),
395
    );
396

            
397
    response.headers_mut().extend(headers);
398
    Ok(response)
399
3
}
400

            
401
pub async fn logout_handler(
402
    cookie_jar: CookieJar,
403
    Extension(auth_guard): Extension<JWTAuthMiddleware>,
404
    State(data): State<Arc<AppState>>,
405
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
406
    let message = t!("Token is invalid or session has expired");
407

            
408
    let refresh_token = cookie_jar
409
        .get("refresh_token")
410
        .map(|cookie| cookie.value().to_string())
411
        .ok_or_else(|| {
412
            let error_response = serde_json::json!({
413
                "status": "fail",
414
                "message": message
415
            });
416
            (StatusCode::FORBIDDEN, Json(error_response))
417
        })?;
418

            
419
    let refresh_token_details =
420
        match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
421
            Ok(token_details) => token_details,
422
            Err(e) => {
423
                let error_response = serde_json::json!({
424
                    "status": "fail",
425
                    "message": format_args!("{:?}", e)
426
                });
427
                return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
428
            }
429
        };
430

            
431
    let mut redis_client = data
432
        .redis_client
433
        .get_multiplexed_async_connection()
434
        .await
435
        .map_err(|e| {
436
            let msg = format!("{} {}", t!("Redis error:"), e);
437
            log::error!("{msg}");
438
            let error_response = serde_json::json!({
439
                "status": "error",
440
                "message": msg,
441
            });
442
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
443
        })?;
444

            
445
    let _: bool = redis_client
446
        .del(&[
447
            refresh_token_details.token_uuid.to_string(),
448
            auth_guard.access_token_uuid.to_string(),
449
        ])
450
        .await
451
        .map_err(|e| {
452
            let error_response = serde_json::json!({
453
                "status": "error",
454
                "message": format_args!("{:?}", e)
455
            });
456
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
457
        })?;
458

            
459
    let access_cookie = Cookie::build(("access_token", ""))
460
        .path("/")
461
        .max_age(time::Duration::minutes(-1))
462
        .same_site(SameSite::Lax)
463
        .http_only(true);
464
    let refresh_cookie = Cookie::build(("refresh_token", ""))
465
        .path("/")
466
        .max_age(time::Duration::minutes(-1))
467
        .same_site(SameSite::Lax)
468
        .http_only(true);
469

            
470
    let logged_in_cookie = Cookie::build(("logged_in", "true"))
471
        .path("/")
472
        .max_age(time::Duration::minutes(-1))
473
        .same_site(SameSite::Lax)
474
        .http_only(false);
475

            
476
    let mut headers = HeaderMap::new();
477
    headers.append(
478
        header::SET_COOKIE,
479
        access_cookie.to_string().parse().unwrap(),
480
    );
481
    headers.append(
482
        header::SET_COOKIE,
483
        refresh_cookie.to_string().parse().unwrap(),
484
    );
485
    headers.append(
486
        header::SET_COOKIE,
487
        logged_in_cookie.to_string().parse().unwrap(),
488
    );
489
    headers.append("HX-Redirect", "/".to_string().parse().unwrap());
490

            
491
    let mut response = Response::new(json!({"status": "success"}).to_string());
492
    response.headers_mut().extend(headers);
493
    Ok(response)
494
}
495

            
496
pub async fn get_me_handler(
497
    Extension(jwtauth): Extension<JWTAuthMiddleware>,
498
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
499
    let json_response = serde_json::json!({
500
        "status":  "success",
501
        "data": serde_json::json!({
502
            "user": filter_user_record(&jwtauth.user)
503
        })
504
    });
505

            
506
    Ok(Json(json_response))
507
}
508

            
509
2
pub async fn get_version() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
510
2
    if let Some(CmdResult::String(version)) = server::command::config::GetVersion::new()
511
2
        .run()
512
2
        .await
513
2
        .map_err(|_| {
514
            (
515
                StatusCode::INTERNAL_SERVER_ERROR,
516
                Json(json!("Can't get version")),
517
            )
518
        })?
519
2
        && let Some(CmdResult::String(build_date)) = server::command::config::GetBuildDate::new()
520
2
            .run()
521
2
            .await
522
2
            .map_err(|_| {
523
                (
524
                    StatusCode::INTERNAL_SERVER_ERROR,
525
                    Json(json!("Can't get version")),
526
                )
527
            })?
528
    {
529
2
        Ok(Response::new(format!(
530
2
            "<span class=\"version\">{version}</span><span class=\"build_date\" data-iso=\"{build_date}\"><script>
531
2
    const el = document.currentScript.parentElement;
532
2
    el.textContent = new Date(el.dataset.iso.trim()).toLocaleString();
533
2
  </script></span>"
534
2
        )))
535
    } else {
536
        Ok(Response::new("Unversioned".to_string()))
537
    }
538
2
}
539

            
540
1
pub async fn get_logout_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
541
1
    Ok(Response::new(
542
1
        "<a hx-get=\"/api/auth/logout\">Logout</a>".to_string(),
543
1
    ))
544
1
}
545

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

            
550
fn filter_user_record(user: &User) -> FilteredUser {
551
    FilteredUser {
552
        id: user.id.to_string(),
553
        email: user.email.clone(),
554
        name: user.name.clone(),
555
        photo: user.photo.clone(),
556
        role: user.role.clone(),
557
        verified: user.verified,
558
        createdAt: user.created_at.unwrap(),
559
        updatedAt: user.updated_at.unwrap(),
560
    }
561
}
562

            
563
fn generate_token(
564
    user_id: uuid::Uuid,
565
    max_age: i64,
566
    private_key: &str,
567
) -> Result<TokenDetails, (StatusCode, Json<serde_json::Value>)> {
568
    token::generate_jwt_token(user_id, max_age, private_key).map_err(|e| {
569
        let msg = format!("{} {}", t!("error generating token:"), e);
570
        log::error!("{msg}");
571
        let error_response = serde_json::json!({
572
            "status": "error",
573
            "message": msg,
574
        });
575
        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
576
    })
577
}
578

            
579
async fn save_token_data_to_redis(
580
    data: &Arc<AppState>,
581
    token_details: &TokenDetails,
582
    max_age: i64,
583
) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
584
    let mut redis_client = data
585
        .redis_client
586
        .get_multiplexed_async_connection()
587
        .await
588
        .map_err(|e| {
589
            let msg = format!("{} {}", t!("Redis error:"), e);
590
            log::error!("{msg}");
591
            let error_response = serde_json::json!({
592
                "status": "error",
593
                "message": msg,
594
            });
595
            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
596
        })?;
597
    let _: bool = redis_client
598
        .set_ex(
599
            token_details.token_uuid.to_string(),
600
            token_details.user_id.to_string(),
601
            (max_age * 60) as u64,
602
        )
603
        .await
604
        .map_err(|e| {
605
            let error_response = serde_json::json!({
606
                "status": "error",
607
                "message": format_args!("{}", e),
608
            });
609
            (StatusCode::UNPROCESSABLE_ENTITY, Json(error_response))
610
        })?;
611
    Ok(())
612
}