summaryrefslogtreecommitdiff
path: root/src/login/routes.rs
blob: c9def2aa43affea8f76232deff932d0fba97ef5d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use axum::{
    extract::{Form, State},
    http::StatusCode,
    response::IntoResponse,
    routing::post,
    Router,
};
use axum_extra::extract::cookie::{Cookie, CookieJar};
use chrono::Utc;
use sqlx::sqlite::SqlitePool;

use crate::error::InternalError;

use super::repo::{logins::Provider as _, tokens::Provider as _};

pub fn router() -> Router<SqlitePool> {
    Router::new().route("/login", post(on_login))
}

#[derive(serde::Deserialize)]
struct Login {
    name: String,
    password: String,
}

async fn on_login(
    State(db): State<SqlitePool>,
    cookies: CookieJar,
    Form(form): Form<Login>,
) -> Result<impl IntoResponse, InternalError> {
    let now = Utc::now();
    let mut tx = db.begin().await?;

    // Spelling the following in the more conventional form,
    //     if let Some(…) = create().await? {}
    //     else if let Some(…) = validate().await? {}
    //     else {}
    // pushes the specifics of whether the returned error types are Send or not
    // (they aren't) into the type of this function's generated Futures, which
    // in turn makes this function unusable as an Axum handler.
    let login = tx.logins().create(&form.name, &form.password).await?;
    let login = if login.is_some() {
        login
    } else {
        tx.logins().authenticate(&form.name, &form.password).await?
    };

    // If `login` is Some, then we have an identity and can issue an identity
    // token. If `login` is None, then neither creating a new login nor authenticating
    // an existing one succeeded, and we must reject the attempt.
    //
    // These properties will be transferred to `token`, as well.
    let token = if let Some(login) = login {
        Some(tx.tokens().issue(&login.id, now).await?)
    } else {
        None
    };

    tx.commit().await?;

    let resp = if let Some(token) = token {
        let cookie = Cookie::build(("identity", token))
            .http_only(true)
            .permanent()
            .build();
        let cookies = cookies.add(cookie);

        (StatusCode::OK, cookies, "logged in")
    } else {
        (
            StatusCode::UNAUTHORIZED,
            cookies,
            "invalid name or password",
        )
    };

    Ok(resp)
}