summaryrefslogtreecommitdiff
path: root/src/api/auth/invite.rs
blob: ecd39f9caec07c1b98b4a6efebb5a8f8ec17599c (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
use base64::URL_SAFE_NO_PAD;
use rocket::{http::uri::Reference, serde::json::Json, State};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

use crate::{api::auth, auth::Authenticated, crypto::random_bytes, db::DbConn, Config};

use super::WithVerifiedFxaLogin;

pub(crate) async fn generate_invite_link(
    db: &DbConn,
    cfg: &Config,
    ttl: Duration,
) -> anyhow::Result<Reference<'static>> {
    let code = base64::encode_config(&random_bytes::<32>(), URL_SAFE_NO_PAD);
    db.add_invite_code(&code, OffsetDateTime::now_utc() + ttl).await?;
    Reference::parse_owned(format!("{}/#/register/{}", cfg.location, code))
        .map_err(|e| anyhow!("url building failed at {e}"))
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct GenerateReq {
    ttl_hours: u32,
}

#[derive(Debug, Serialize)]
pub(crate) struct GenerateResp {
    url: Reference<'static>,
}

#[post("/generate", data = "<req>")]
pub(crate) async fn generate(
    db: &DbConn,
    cfg: &State<Config>,
    req: Authenticated<GenerateReq, WithVerifiedFxaLogin>,
) -> auth::Result<GenerateResp> {
    if !req.context.verified {
        return Err(auth::Error::UnverifiedSession);
    }
    let user = db.get_user_by_id(&req.context.uid).await?;
    if user.email != cfg.invite_admin_address {
        return Err(auth::Error::InvalidAuthToken);
    }
    let url = generate_invite_link(db, cfg, Duration::hours(req.body.ttl_hours as i64)).await?;
    Ok(Json(GenerateResp { url }))
}