use base64::URL_SAFE_NO_PAD; use chrono::{Duration, Utc}; use rocket::{http::uri::Reference, serde::json::Json, State}; use serde::{Deserialize, Serialize}; 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> { let code = base64::encode_config(&random_bytes::<32>(), URL_SAFE_NO_PAD); db.add_invite_code(&code, Utc::now() + 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 = "")] pub(crate) async fn generate( db: &DbConn, cfg: &State, req: Authenticated, ) -> auth::Result { 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 })) }