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 { pub email: Option, pub handle: String, pub did: Option, pub invite_code: Option, pub verification_code: Option, pub verification_phone: Option, pub password: Option, pub recovery_key: Option, pub plc_op: Option, } #[derive(Serialize, Debug)] pub struct CreateAccountResponse { pub handle: String, pub did: String, // pub did_doc: Option, // TODO: Define DidDocument type pub access_jwt: String, pub refresh_jwt: String, } #[instrument] pub async fn create_account(data: ProcedureInput) -> Response { event!(Level::INFO, "Creating account for handle: {}", data.input.handle); // TODO: Implement the following steps based on the TypeScript reference: // 1. Input validation let validated_input = match validate_inputs(&data.input).await { Ok(input) => input, Err(err) => return err, }; // 2. Check handle and email availability if let Err(err) = check_availability(&validated_input).await { return err; } // 3. Generate DID and signing key let (did, signing_key, plc_op) = match generate_identity(&validated_input).await { Ok(identity) => identity, Err(err) => return err, }; // 4. Create actor store entry if let Err(err) = create_actor_store(&did, &signing_key).await { return err; } // 5. Create repository let repo_commit = match create_repository(&did).await { Ok(commit) => commit, Err(err) => return err, }; // 6. Submit PLC operation (if needed) if let Some(op) = plc_op { if let Err(err) = submit_plc_operation(&did, &op).await { return err; } } // 7. Create account and session let credentials = match create_account_and_session(&validated_input, &did, &repo_commit).await { Ok(creds) => creds, Err(err) => return err, }; // 8. Sequence events (identity, account, commit, sync) if let Err(err) = sequence_events(&did, &validated_input.handle, &repo_commit).await { return err; } // 9. Update repo root if let Err(err) = update_repo_root(&did, &repo_commit).await { return err; } // Return success response let response = CreateAccountResponse { handle: validated_input.handle, did: did.clone(), access_jwt: credentials.access_jwt, refresh_jwt: credentials.refresh_jwt, }; event!(Level::INFO, "Account created successfully for DID: {}", did); // TODO: Replace with proper JSON response encoding 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 { // Based on validateInputsForLocalPds in the TypeScript version // 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> { // Check that handle and email are not already taken todo!("Implement availability checking") } async fn generate_identity(input: &ValidatedInput) -> Result<(String, SigningKey, Option), Response> { // Generate signing key // Create DID and PLC operation if not provided todo!("Implement identity generation") } async fn create_actor_store(did: &str, signing_key: &SigningKey) -> Result<(), Response> { // Create actor store entry for the new account todo!("Implement actor store creation") } async fn create_repository(did: &str) -> Result { // Create empty repository for the account todo!("Implement repository creation") } async fn submit_plc_operation(did: &str, plc_op: &PlcOp) -> Result<(), Response> { // Submit PLC operation to register/update DID todo!("Implement PLC operation submission") } async fn create_account_and_session( input: &ValidatedInput, did: &str, repo_commit: &RepoCommit, ) -> Result { // Create account record and initial session // Generate JWT tokens todo!("Implement account and session creation") } async fn sequence_events(did: &str, handle: &str, repo_commit: &RepoCommit) -> Result<(), Response> { // Sequence identity, account, commit, and sync events todo!("Implement event sequencing") } async fn update_repo_root(did: &str, repo_commit: &RepoCommit) -> Result<(), Response> { // Update repository root reference todo!("Implement repo root update") } // TODO: Define these types based on our implementation needs #[derive(Debug)] struct ValidatedInput { handle: String, email: String, password: Option, invite_code: Option, } #[derive(Debug)] struct SigningKey { // TODO: Define signing key structure } #[derive(Debug)] struct PlcOp { // TODO: Define PLC operation structure } #[derive(Debug)] struct RepoCommit { cid: String, rev: String, } #[derive(Debug)] struct Credentials { access_jwt: String, refresh_jwt: String, }