chore: migrate authifier into codebase (#658)

Co-authored-by: izzy <me@insrt.uk>
Signed-off-by: Zomatree <me@zomatree.live>
Signed-off-by: izzy <me@insrt.uk>
This commit is contained in:
Zomatree
2026-06-21 00:50:06 +01:00
committed by GitHub
co-authored by izzy
parent a7af24b38d
commit d27917b824
145 changed files with 108392 additions and 1189 deletions
@@ -0,0 +1,104 @@
//! Verify an account
//! POST /verify/<code>
use rocket::{serde::json::Json, State};
use revolt_database::{Database, EmailVerification, MFATicket, util::email::normalise_email};
use revolt_result::Result;
use revolt_models::v0;
/// # Verify Email
///
/// Verify an email address.
#[openapi(tag = "Account")]
#[post("/verify/<code>")]
pub async fn verify_email(
db: &State<Database>,
code: String,
) -> Result<Json<v0::ResponseVerify>> {
// Find the account
let mut account = db
.fetch_account_with_email_verification(&code)
.await?;
// Update account email
let response = if let EmailVerification::Moving { new_email, .. } = &account.verification {
account.email = new_email.clone();
account.email_normalised = normalise_email(new_email.clone());
v0::ResponseVerify::NoTicket
} else {
let mut ticket = MFATicket::new(account.id.to_string(), false);
ticket.authorised = true;
ticket.save(db).await?;
v0::ResponseVerify::WithTicket { ticket: ticket.into() }
};
// Mark as verified
account.verification = EmailVerification::Verified;
// Save to database
account.save(db).await?;
Ok(Json(response))
}
#[cfg(test)]
mod tests {
use iso8601_timestamp::{Timestamp, Duration};
use revolt_database::EmailVerification;
use crate::{rocket, util::test::TestHarness};
use rocket::http::{ContentType, Status};
use revolt_models::v0;
use revolt_result::{Error, ErrorType};
#[rocket::async_test]
async fn success() {
let harness = TestHarness::new().await;
let (mut account, _, _) = harness.new_user().await;
account.verification = EmailVerification::Pending {
token: "token".into(),
expiry: Timestamp::now_utc() + Duration::seconds(100),
};
account.save(&harness.db).await.unwrap();
let res = harness.client.post("/auth/account/verify/token").dispatch().await;
assert_eq!(res.status(), Status::Ok);
// Make sure it was used and can't be used again
assert!(harness.db
.fetch_account_with_email_verification("token")
.await
.is_err());
// Check that we can login using the received MFA ticket
let response = res.into_json::<v0::ResponseVerify>().await
.expect("`ResponseVerify`");
if let v0::ResponseVerify::WithTicket { ticket } = response {
let res = harness.client
.post("/auth/session/login")
.header(ContentType::JSON)
.body(json!({ "mfa_ticket": ticket.token }).to_string())
.dispatch()
.await;
assert_eq!(res.status(), Status::Ok);
assert!(res.into_json::<v0::Session>().await.is_some());
} else {
panic!("Expected `ResponseVerify::WithTicket`");
}
}
#[rocket::async_test]
async fn fail_invalid_token() {
let harness = TestHarness::new().await;
let res = harness.client.post("/auth/account/verify/token").dispatch().await;
assert_eq!(res.status(), Status::Unauthorized);
assert!(matches!(
res.into_json::<Error>().await.unwrap().error_type,
ErrorType::InvalidToken,
));
}
}