summaryrefslogtreecommitdiff
path: root/src/api/auth/email.rs
blob: f206759add3df193505595fb9ad246b7b0965416 (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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::sync::Arc;

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

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

// MISSING get /recovery_emails
// MISSING post /recovery_email
// MISSING post /recovery_email/destroy
// MISSING post /recovery_email/resend_code
// MISSING post /recovery_email/set_primary
// MISSING post /emails/reminders/cad
// MISSING post /recovery_email/secondary/resend_code
// MISSING post /recovery_email/secondary/verify_code

#[derive(Debug, Serialize)]
#[allow(non_snake_case)]
pub(crate) struct StatusResp {
    email: String,
    verified: bool,
    sessionVerified: bool,
    emailVerified: bool,
}

// MISSING arg: reason
#[get("/recovery_email/status")]
pub(crate) async fn status(
    db: &DbConn,
    req: Authenticated<(), WithFxaLogin>,
) -> auth::Result<StatusResp> {
    let user = db.get_user_by_id(&req.context.uid).await?;
    Ok(Json(StatusResp {
        email: user.email,
        verified: user.verified,
        sessionVerified: req.context.verified,
        emailVerified: user.verified,
    }))
}

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

#[post("/recovery_email/verify_code", data = "<req>")]
pub(crate) async fn verify_code(
    db: &DbConn,
    db_pool: &Db,
    defer: &DeferAction,
    pc: &State<Arc<PushClient>>,
    req: Json<VerifyReq>,
) -> auth::Result<Empty> {
    let code = match db.try_use_verify_code(&req.uid, &req.code).await? {
        Some(code) => code,
        None => return Err(auth::Error::InvalidVerificationCode),
    };
    db.set_user_verified(&req.uid).await?;
    if let Some(sid) = code.session_id {
        db.set_session_verified(&sid).await?;
    }
    match db.get_devices(&req.uid).await {
        Ok(devs) => defer.spawn_after_success("api::auth/recovery_email/verify_code(post)", {
            let (pc, db) = (Arc::clone(pc), db_pool.clone());
            async move {
                let db = db.begin().await?;
                pc.account_verified(&db, &devs).await;
                db.commit().await?;
                Ok(())
            }
        }),
        Err(e) => warn!("account_verified push failed: {e}"),
    }
    Ok(EMPTY)
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ResendReq {
    // MISSING email
    // MISSING service
    // MISSING redirectTo
    // MISSING resume
    // MISSING style
    // MISSING type
}

// MISSING arg: service
// MISSING arg: type
#[post("/recovery_email/resend_code", data = "<req>")]
pub(crate) async fn resend_code(
    db: &DbConn,
    mailer: &State<Arc<Mailer>>,
    req: Authenticated<ResendReq, WithFxaLogin>,
) -> auth::Result<Empty> {
    let (email, code) = match db.get_verify_code(&req.context.uid).await {
        Ok(v) => v,
        Err(_) => return Err(auth::Error::InvalidVerificationCode),
    };
    // NOTE we send the email in this context rather than a spawn to signal
    // send errors to the client.
    mailer.send_account_verify(&req.context.uid, &email, &code.code).await.map_err(|e| {
        error!("failed to send email: {e}");
        auth::Error::EmailFailed
    })?;
    Ok(EMPTY)
}