summaryrefslogtreecommitdiff
path: root/src/api/auth/session.rs
blob: 5911b92ad8f279b95fa39f8ebf5415f6c01a6201 (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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::sync::Arc;

use rocket::serde::json::Json;
use rocket::State;
use serde::{Deserialize, Serialize};

use crate::api::auth::WithFxaLogin;
use crate::api::{auth, Empty, EMPTY};
use crate::auth::Authenticated;
use crate::db::Db;
use crate::db::DbConn;
use crate::mailer::Mailer;
use crate::push::PushClient;
use crate::types::{SessionID, UserID};
use crate::utils::DeferAction;

// MISSING post /session/duplicate
// MISSING post /session/reauth
// MISSING post /session/verify/send_push

#[derive(Debug, Serialize)]
pub(crate) struct StatusResp {
    state: &'static str, // what does this *do*?
    uid: UserID,
}

#[get("/session/status")]
pub(crate) async fn status(req: Authenticated<(), WithFxaLogin>) -> auth::Result<StatusResp> {
    Ok(Json(StatusResp { state: "", uid: req.context.uid }))
}

#[post("/session/resend_code", data = "<req>")]
pub(crate) async fn resend_code(
    db: &DbConn,
    mailer: &State<Arc<Mailer>>,
    req: Authenticated<Empty, WithFxaLogin>,
) -> auth::Result<Empty> {
    let code = match req.context.verify_code {
        Some(code) => code,
        _ => return Err(auth::Error::InvalidVerificationCode),
    };

    let user = db.get_user_by_id(&req.context.uid).await?;
    mailer.send_session_verify(&user.email, &code).await.map_err(|e| {
        error!("failed to send email: {e}");
        auth::Error::EmailFailed
    })?;
    Ok(EMPTY)
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct VerifyReq {
    code: String,
    // MISSING service
    // MISSING scopes
    // MISSING marketingOptIn
    // MISSING newsletters
}

#[post("/session/verify_code", data = "<req>")]
pub(crate) async fn verify_code(
    db: &DbConn,
    req: Authenticated<VerifyReq, WithFxaLogin>,
) -> auth::Result<Empty> {
    if req.context.verify_code.as_ref() != Some(&req.body.code) {
        return Err(auth::Error::InvalidVerificationCode);
    }
    db.set_session_verified(&req.session).await?;
    Ok(EMPTY)
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct DestroyReq {
    custom_session_id: Option<SessionID>,
}

#[post("/session/destroy", data = "<data>")]
pub(crate) async fn destroy(
    db: &DbConn,
    db_pool: &Db,
    defer: &DeferAction,
    client: &State<Arc<PushClient>>,
    data: Authenticated<DestroyReq, WithFxaLogin>,
) -> auth::Result<Empty> {
    if data.body.custom_session_id.is_some() && !data.context.verified {
        return Err(auth::Error::UnverifiedSession);
    }
    let id = data.body.custom_session_id.as_ref().unwrap_or(&data.session);
    db.delete_session(&data.context.uid, id).await.map_err(|_| auth::Error::UnknownDevice)?;
    if let Some(id) = data.context.device_id {
        match db.get_devices(&data.context.uid).await {
            Err(e) => warn!("device_disconnected push failed: {e}"),
            Ok(devs) => defer.spawn_after_success("api::auth/session/destroy(post)", {
                let (client, db) = (Arc::clone(client), db_pool.clone());
                async move {
                    let db = db.begin().await?;
                    client.device_disconnected(&db, &devs, &id).await;
                    db.commit().await?;
                    Ok(())
                }
            }),
        };
    }
    Ok(EMPTY)
}