WIP: Implemented input validation for createAccount

This commit is contained in:
Julia Lange 2025-08-29 13:11:40 -07:00
parent 7eb0be102e
commit b522c062c0
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto

View file

@ -2,6 +2,8 @@ use router::xrpc::{ProcedureInput, Response, error};
use serde::{Deserialize, Serialize};
use http::status::StatusCode;
use tracing::{event, instrument, Level};
use atproto::types::Handle;
use std::str::FromStr;
#[derive(Deserialize, Debug)]
pub struct CreateAccountInput {
@ -96,15 +98,85 @@ pub async fn create_account(data: ProcedureInput<CreateAccountInput>) -> Respons
error(StatusCode::OK, "success", "Account created successfully")
}
// Maximum password length (matches atproto TypeScript implementation)
const NEW_PASSWORD_MAX_LENGTH: usize = 256;
// TODO: Implement these helper functions
async fn validate_inputs(input: &CreateAccountInput) -> Result<ValidatedInput, Response> {
// Based on validateInputsForLocalPds in the TypeScript version
// - Validate email format and not disposable
// - Validate password length
// - Check invite code if required
// - Normalize and validate handle
todo!("Implement input validation")
// Validate email is provided and has basic format
let email = match &input.email {
Some(e) if !e.is_empty() => e.clone(),
_ => {
return Err(error(
StatusCode::BAD_REQUEST,
"InvalidRequest",
"Email is required"
));
}
};
// Validate email format (basic validation for now)
// TODO: Improve email validation - add proper RFC validation and disposable email checking
// TypeScript version uses @hapi/address for validation and disposable-email-domains-js for disposable check
if !is_valid_email(&email) {
return Err(error(
StatusCode::BAD_REQUEST,
"InvalidRequest",
"This email address is not supported, please use a different email."
));
}
// Validate password length if provided
if let Some(password) = &input.password {
if password.len() > NEW_PASSWORD_MAX_LENGTH {
return Err(error(
StatusCode::BAD_REQUEST,
"InvalidRequest",
&format!("Password too long. Maximum length is {} characters.", NEW_PASSWORD_MAX_LENGTH)
));
}
}
// Validate and normalize handle using atproto types
let handle = Handle::from_str(&input.handle).map_err(|_| {
error(
StatusCode::BAD_REQUEST,
"InvalidRequest",
"Invalid handle format"
)
})?;
// TODO: Invite codes - not supported for now but leave placeholder
if input.invite_code.is_some() {
event!(Level::INFO, "Invite codes not yet supported, ignoring");
}
Ok(ValidatedInput {
handle: handle.to_string(),
email: email.to_lowercase(), // Normalize email to lowercase
password: input.password.clone(),
invite_code: input.invite_code.clone(),
})
}
// Basic email validation - checks for @ and . in reasonable positions
// TODO: Replace with proper email validation library like email-address crate
fn is_valid_email(email: &str) -> bool {
// Very basic email validation
let at_pos = email.find('@');
let last_dot_pos = email.rfind('.');
match (at_pos, last_dot_pos) {
(Some(at), Some(dot)) => {
// @ must come before the last dot
// Must have content before @, between @ and dot, and after dot
at > 0 && dot > at + 1 && dot < email.len() - 1
}
_ => false,
}
}
async fn check_availability(input: &ValidatedInput) -> Result<(), Response> {