mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-25 03:34:13 +00:00
Identity endpoint conformance vs ref
This commit is contained in:
+99
-23
@@ -511,7 +511,14 @@ pub async fn get_recommended_did_credentials(
|
||||
let rotation_keys = if auth_user.did.starts_with("did:web:") {
|
||||
vec![]
|
||||
} else {
|
||||
vec![did_key.clone()]
|
||||
let server_rotation_key = match std::env::var("PLC_ROTATION_KEY") {
|
||||
Ok(key) => key,
|
||||
Err(_) => {
|
||||
warn!("PLC_ROTATION_KEY not set, falling back to user's signing key for rotation key recommendation");
|
||||
did_key.clone()
|
||||
}
|
||||
};
|
||||
vec![server_rotation_key]
|
||||
};
|
||||
(
|
||||
StatusCode::OK,
|
||||
@@ -559,20 +566,45 @@ pub async fn update_handle(
|
||||
return e;
|
||||
}
|
||||
let did = auth_user.did;
|
||||
let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
|
||||
.fetch_optional(&state.db)
|
||||
if !state
|
||||
.check_rate_limit(crate::state::RateLimitKind::HandleUpdate, &did)
|
||||
.await
|
||||
{
|
||||
Ok(Some(id)) => id,
|
||||
return (
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
Json(json!({"error": "RateLimitExceeded", "message": "Too many handle updates. Try again later."})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if !state
|
||||
.check_rate_limit(crate::state::RateLimitKind::HandleUpdateDaily, &did)
|
||||
.await
|
||||
{
|
||||
return (
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
Json(json!({"error": "RateLimitExceeded", "message": "Daily handle update limit exceeded."})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let user_row = match sqlx::query!(
|
||||
"SELECT id, handle FROM users WHERE did = $1",
|
||||
did
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(Some(row)) => row,
|
||||
_ => return ApiError::InternalError.into_response(),
|
||||
};
|
||||
let new_handle = input.handle.trim();
|
||||
let user_id = user_row.id;
|
||||
let current_handle = user_row.handle;
|
||||
let new_handle = input.handle.trim().to_ascii_lowercase();
|
||||
if new_handle.is_empty() {
|
||||
return ApiError::InvalidRequest("handle is required".into()).into_response();
|
||||
}
|
||||
if !new_handle
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_')
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
|
||||
{
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
@@ -582,7 +614,23 @@ pub async fn update_handle(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if crate::moderation::has_explicit_slur(new_handle) {
|
||||
for segment in new_handle.split('.') {
|
||||
if segment.is_empty() {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidHandle", "message": "Handle contains empty segment"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if segment.starts_with('-') || segment.ends_with('-') {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidHandle", "message": "Handle segment cannot start or end with hyphen"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
if crate::moderation::has_explicit_slur(&new_handle) {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidHandle", "message": "Inappropriate language in handle"})),
|
||||
@@ -591,13 +639,27 @@ pub async fn update_handle(
|
||||
}
|
||||
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
|
||||
let suffix = format!(".{}", hostname);
|
||||
let is_service_domain = crate::handle::is_service_domain_handle(new_handle, &hostname);
|
||||
let is_service_domain = crate::handle::is_service_domain_handle(&new_handle, &hostname);
|
||||
let handle = if is_service_domain {
|
||||
let short_part = if new_handle.ends_with(&suffix) {
|
||||
new_handle.strip_suffix(&suffix).unwrap_or(new_handle)
|
||||
new_handle.strip_suffix(&suffix).unwrap_or(&new_handle)
|
||||
} else {
|
||||
new_handle
|
||||
&new_handle
|
||||
};
|
||||
let full_handle = if new_handle.ends_with(&suffix) {
|
||||
new_handle.clone()
|
||||
} else {
|
||||
format!("{}.{}", new_handle, hostname)
|
||||
};
|
||||
if full_handle == current_handle {
|
||||
if let Err(e) =
|
||||
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&full_handle))
|
||||
.await
|
||||
{
|
||||
warn!("Failed to sequence identity event for handle update: {}", e);
|
||||
}
|
||||
return (StatusCode::OK, Json(json!({}))).into_response();
|
||||
}
|
||||
if short_part.contains('.') {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
@@ -608,13 +670,32 @@ pub async fn update_handle(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if new_handle.ends_with(&suffix) {
|
||||
new_handle.to_string()
|
||||
} else {
|
||||
format!("{}.{}", new_handle, hostname)
|
||||
if short_part.len() < 3 {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidHandle", "message": "Handle too short"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if short_part.len() > 18 {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidHandle", "message": "Handle too long"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
full_handle
|
||||
} else {
|
||||
match crate::handle::verify_handle_ownership(new_handle, &did).await {
|
||||
if new_handle == current_handle {
|
||||
if let Err(e) =
|
||||
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&new_handle))
|
||||
.await
|
||||
{
|
||||
warn!("Failed to sequence identity event for handle update: {}", e);
|
||||
}
|
||||
return (StatusCode::OK, Json(json!({}))).into_response();
|
||||
}
|
||||
match crate::handle::verify_handle_ownership(&new_handle, &did).await {
|
||||
Ok(()) => {}
|
||||
Err(crate::handle::HandleResolutionError::NotFound) => {
|
||||
return (
|
||||
@@ -649,13 +730,8 @@ pub async fn update_handle(
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
new_handle.to_string()
|
||||
new_handle.clone()
|
||||
};
|
||||
let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE id = $1", user_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let existing = sqlx::query!(
|
||||
"SELECT id FROM users WHERE handle = $1 AND id != $2",
|
||||
handle,
|
||||
@@ -679,8 +755,8 @@ pub async fn update_handle(
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
if let Some(old) = old_handle {
|
||||
let _ = state.cache.delete(&format!("handle:{}", old)).await;
|
||||
if !current_handle.is_empty() {
|
||||
let _ = state.cache.delete(&format!("handle:{}", current_handle)).await;
|
||||
}
|
||||
let _ = state.cache.delete(&format!("handle:{}", handle)).await;
|
||||
if let Err(e) =
|
||||
|
||||
@@ -23,13 +23,11 @@ pub async fn submit_plc_operation(
|
||||
headers: axum::http::HeaderMap,
|
||||
Json(input): Json<SubmitPlcOperationInput>,
|
||||
) -> Response {
|
||||
info!("[MIGRATION] submitPlcOperation called");
|
||||
let bearer = match crate::auth::extract_bearer_token_from_header(
|
||||
headers.get("Authorization").and_then(|h| h.to_str().ok()),
|
||||
) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
info!("[MIGRATION] submitPlcOperation: No bearer token");
|
||||
return ApiError::AuthenticationRequired.into_response();
|
||||
}
|
||||
};
|
||||
@@ -37,20 +35,14 @@ pub async fn submit_plc_operation(
|
||||
match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &bearer).await {
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
info!("[MIGRATION] submitPlcOperation: Auth failed: {:?}", e);
|
||||
return ApiError::from(e).into_response();
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Authenticated user did={}",
|
||||
auth_user.did
|
||||
);
|
||||
if let Err(e) = crate::auth::scope_check::check_identity_scope(
|
||||
auth_user.is_oauth,
|
||||
auth_user.scope.as_deref(),
|
||||
crate::oauth::scopes::IdentityAttr::Wildcard,
|
||||
) {
|
||||
info!("[MIGRATION] submitPlcOperation: Scope check failed");
|
||||
return e;
|
||||
}
|
||||
let did = &auth_user.did;
|
||||
@@ -67,7 +59,7 @@ pub async fn submit_plc_operation(
|
||||
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
|
||||
let public_url = format!("https://{}", hostname);
|
||||
let user = match sqlx::query!(
|
||||
"SELECT id, handle, deactivated_at FROM users WHERE did = $1",
|
||||
"SELECT id, handle FROM users WHERE did = $1",
|
||||
did
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
@@ -82,7 +74,6 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let is_migration = user.deactivated_at.is_some();
|
||||
let key_row = match sqlx::query!(
|
||||
"SELECT key_bytes, encryption_version FROM user_keys WHERE user_id = $1",
|
||||
user.id
|
||||
@@ -123,10 +114,9 @@ pub async fn submit_plc_operation(
|
||||
}
|
||||
};
|
||||
let user_did_key = signing_key_to_did_key(&signing_key);
|
||||
if !is_migration && let Some(rotation_keys) = op.get("rotationKeys").and_then(|v| v.as_array())
|
||||
{
|
||||
let server_rotation_key =
|
||||
std::env::var("PLC_ROTATION_KEY").unwrap_or_else(|_| user_did_key.clone());
|
||||
let server_rotation_key =
|
||||
std::env::var("PLC_ROTATION_KEY").unwrap_or_else(|_| user_did_key.clone());
|
||||
if let Some(rotation_keys) = op.get("rotationKeys").and_then(|v| v.as_array()) {
|
||||
let has_server_key = rotation_keys
|
||||
.iter()
|
||||
.any(|k| k.as_str() == Some(&server_rotation_key));
|
||||
@@ -167,21 +157,20 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
if !is_migration {
|
||||
if let Some(verification_methods) =
|
||||
op.get("verificationMethods").and_then(|v| v.as_object())
|
||||
&& let Some(atproto_key) = verification_methods.get("atproto").and_then(|v| v.as_str())
|
||||
&& atproto_key != user_did_key
|
||||
{
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": "InvalidRequest",
|
||||
"message": "Incorrect signing key in verificationMethods"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if let Some(verification_methods) = op.get("verificationMethods").and_then(|v| v.as_object())
|
||||
&& let Some(atproto_key) = verification_methods.get("atproto").and_then(|v| v.as_str())
|
||||
&& atproto_key != user_did_key
|
||||
{
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": "InvalidRequest",
|
||||
"message": "Incorrect signing key in verificationMethods"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if !user.handle.is_empty() {
|
||||
if let Some(also_known_as) = op.get("alsoKnownAs").and_then(|v| v.as_array()) {
|
||||
let expected_handle = format!("at://{}", user.handle);
|
||||
let first_aka = also_known_as.first().and_then(|v| v.as_str());
|
||||
@@ -200,11 +189,6 @@ pub async fn submit_plc_operation(
|
||||
let plc_client = PlcClient::new(None);
|
||||
let operation_clone = input.operation.clone();
|
||||
let did_clone = did.clone();
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Sending operation to PLC directory for did={}",
|
||||
did
|
||||
);
|
||||
let plc_start = std::time::Instant::now();
|
||||
let result: Result<(), CircuitBreakerError<PlcError>> =
|
||||
with_circuit_breaker(&state.circuit_breakers.plc_directory, || async {
|
||||
plc_client
|
||||
@@ -213,17 +197,9 @@ pub async fn submit_plc_operation(
|
||||
})
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => {
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: PLC directory accepted operation in {:?}",
|
||||
plc_start.elapsed()
|
||||
);
|
||||
}
|
||||
Ok(()) => {}
|
||||
Err(CircuitBreakerError::CircuitOpen(e)) => {
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: PLC directory circuit breaker open: {}",
|
||||
e
|
||||
);
|
||||
warn!("PLC directory circuit breaker open: {}", e);
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(json!({
|
||||
@@ -234,10 +210,7 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
Err(CircuitBreakerError::OperationFailed(e)) => {
|
||||
error!(
|
||||
"[MIGRATION] submitPlcOperation: PLC operation failed: {:?}",
|
||||
e
|
||||
);
|
||||
error!("PLC operation failed: {:?}", e);
|
||||
return (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(json!({
|
||||
@@ -248,10 +221,6 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Sequencing identity event for did={}",
|
||||
did
|
||||
);
|
||||
match sqlx::query!(
|
||||
"INSERT INTO repo_seq (did, event_type) VALUES ($1, 'identity') RETURNING seq",
|
||||
did
|
||||
@@ -260,27 +229,21 @@ pub async fn submit_plc_operation(
|
||||
.await
|
||||
{
|
||||
Ok(row) => {
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Identity event sequenced with seq={}",
|
||||
row.seq
|
||||
);
|
||||
if let Err(e) = sqlx::query(&format!("NOTIFY repo_updates, '{}'", row.seq))
|
||||
.execute(&state.db)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: Failed to notify identity event: {:?}",
|
||||
e
|
||||
);
|
||||
warn!("Failed to notify identity event: {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: Failed to sequence identity event: {:?}",
|
||||
e
|
||||
);
|
||||
warn!("Failed to sequence identity event: {:?}", e);
|
||||
}
|
||||
}
|
||||
info!("[MIGRATION] submitPlcOperation: SUCCESS for did={}", did);
|
||||
let _ = state.cache.delete(&format!("handle:{}", user.handle)).await;
|
||||
if state.did_resolver.refresh_did(did).await.is_none() {
|
||||
warn!(did = %did, "Failed to refresh DID cache after PLC update");
|
||||
}
|
||||
info!(did = %did, "PLC operation submitted successfully");
|
||||
(StatusCode::OK, Json(json!({}))).into_response()
|
||||
}
|
||||
|
||||
+12
-12
@@ -35,12 +35,12 @@ impl std::fmt::Display for HandleValidationError {
|
||||
),
|
||||
Self::InvalidCharacters => write!(
|
||||
f,
|
||||
"Handle contains invalid characters. Only alphanumeric, hyphens, and underscores are allowed"
|
||||
"Handle contains invalid characters. Only alphanumeric characters and hyphens are allowed"
|
||||
),
|
||||
Self::StartsWithInvalidChar => {
|
||||
write!(f, "Handle cannot start with a hyphen or underscore")
|
||||
write!(f, "Handle cannot start with a hyphen")
|
||||
}
|
||||
Self::EndsWithInvalidChar => write!(f, "Handle cannot end with a hyphen or underscore"),
|
||||
Self::EndsWithInvalidChar => write!(f, "Handle cannot end with a hyphen"),
|
||||
Self::ContainsSpaces => write!(f, "Handle cannot contain spaces"),
|
||||
Self::BannedWord => write!(f, "Inappropriate language in handle"),
|
||||
}
|
||||
@@ -67,19 +67,19 @@ pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationErr
|
||||
}
|
||||
|
||||
if let Some(first_char) = handle.chars().next()
|
||||
&& (first_char == '-' || first_char == '_')
|
||||
&& first_char == '-'
|
||||
{
|
||||
return Err(HandleValidationError::StartsWithInvalidChar);
|
||||
}
|
||||
|
||||
if let Some(last_char) = handle.chars().last()
|
||||
&& (last_char == '-' || last_char == '_')
|
||||
&& last_char == '-'
|
||||
{
|
||||
return Err(HandleValidationError::EndsWithInvalidChar);
|
||||
}
|
||||
|
||||
for c in handle.chars() {
|
||||
if !c.is_ascii_alphanumeric() && c != '-' && c != '_' {
|
||||
if !c.is_ascii_alphanumeric() && c != '-' {
|
||||
return Err(HandleValidationError::InvalidCharacters);
|
||||
}
|
||||
}
|
||||
@@ -150,10 +150,6 @@ mod tests {
|
||||
validate_short_handle("user-name"),
|
||||
Ok("user-name".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("user_name"),
|
||||
Ok("user_name".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("UPPERCASE"),
|
||||
Ok("uppercase".to_string())
|
||||
@@ -194,7 +190,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("_starts"),
|
||||
Err(HandleValidationError::StartsWithInvalidChar)
|
||||
Err(HandleValidationError::InvalidCharacters)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("ends-"),
|
||||
@@ -202,7 +198,11 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("ends_"),
|
||||
Err(HandleValidationError::EndsWithInvalidChar)
|
||||
Err(HandleValidationError::InvalidCharacters)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("user_name"),
|
||||
Err(HandleValidationError::InvalidCharacters)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("test@user"),
|
||||
|
||||
@@ -110,6 +110,14 @@ impl DidResolver {
|
||||
Some(resolved)
|
||||
}
|
||||
|
||||
pub async fn refresh_did(&self, did: &str) -> Option<ResolvedService> {
|
||||
{
|
||||
let mut cache = self.did_cache.write().await;
|
||||
cache.remove(did);
|
||||
}
|
||||
self.resolve_did(did).await
|
||||
}
|
||||
|
||||
async fn resolve_did_internal(&self, did: &str) -> Option<ResolvedService> {
|
||||
let did_doc = if did.starts_with("did:web:") {
|
||||
self.resolve_did_web(did).await
|
||||
|
||||
@@ -93,6 +93,9 @@ pub async fn verify_handle_ownership(
|
||||
}
|
||||
|
||||
pub fn is_service_domain_handle(handle: &str, hostname: &str) -> bool {
|
||||
if !handle.contains('.') {
|
||||
return true;
|
||||
}
|
||||
let service_domains: Vec<String> = std::env::var("PDS_SERVICE_HANDLE_DOMAINS")
|
||||
.map(|s| s.split(',').map(|d| d.trim().to_string()).collect())
|
||||
.unwrap_or_else(|_| vec![hostname.to_string()]);
|
||||
@@ -115,6 +118,7 @@ mod tests {
|
||||
fn test_is_service_domain_handle() {
|
||||
assert!(is_service_domain_handle("user.example.com", "example.com"));
|
||||
assert!(is_service_domain_handle("example.com", "example.com"));
|
||||
assert!(is_service_domain_handle("myhandle", "example.com"));
|
||||
assert!(!is_service_domain_handle("user.other.com", "example.com"));
|
||||
assert!(!is_service_domain_handle("myhandle.xyz", "example.com"));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ pub struct RateLimiters {
|
||||
pub app_password: Arc<KeyedRateLimiter>,
|
||||
pub email_update: Arc<KeyedRateLimiter>,
|
||||
pub totp_verify: Arc<KeyedRateLimiter>,
|
||||
pub handle_update: Arc<KeyedRateLimiter>,
|
||||
pub handle_update_daily: Arc<KeyedRateLimiter>,
|
||||
}
|
||||
|
||||
impl Default for RateLimiters {
|
||||
@@ -79,6 +81,16 @@ impl RateLimiters {
|
||||
.unwrap()
|
||||
.allow_burst(NonZeroU32::new(5).unwrap()),
|
||||
)),
|
||||
handle_update: Arc::new(RateLimiter::keyed(
|
||||
Quota::with_period(std::time::Duration::from_secs(30))
|
||||
.unwrap()
|
||||
.allow_burst(NonZeroU32::new(10).unwrap()),
|
||||
)),
|
||||
handle_update_daily: Arc::new(RateLimiter::keyed(
|
||||
Quota::with_period(std::time::Duration::from_secs(1728))
|
||||
.unwrap()
|
||||
.allow_burst(NonZeroU32::new(50).unwrap()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ pub enum RateLimitKind {
|
||||
AppPassword,
|
||||
EmailUpdate,
|
||||
TotpVerify,
|
||||
HandleUpdate,
|
||||
HandleUpdateDaily,
|
||||
}
|
||||
|
||||
impl RateLimitKind {
|
||||
@@ -54,6 +56,8 @@ impl RateLimitKind {
|
||||
Self::AppPassword => "app_password",
|
||||
Self::EmailUpdate => "email_update",
|
||||
Self::TotpVerify => "totp_verify",
|
||||
Self::HandleUpdate => "handle_update",
|
||||
Self::HandleUpdateDaily => "handle_update_daily",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +75,8 @@ impl RateLimitKind {
|
||||
Self::AppPassword => (10, 60_000),
|
||||
Self::EmailUpdate => (5, 3_600_000),
|
||||
Self::TotpVerify => (5, 300_000),
|
||||
Self::HandleUpdate => (10, 300_000),
|
||||
Self::HandleUpdateDaily => (50, 86_400_000),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,6 +197,8 @@ impl AppState {
|
||||
RateLimitKind::AppPassword => &self.rate_limiters.app_password,
|
||||
RateLimitKind::EmailUpdate => &self.rate_limiters.email_update,
|
||||
RateLimitKind::TotpVerify => &self.rate_limiters.totp_verify,
|
||||
RateLimitKind::HandleUpdate => &self.rate_limiters.handle_update,
|
||||
RateLimitKind::HandleUpdateDaily => &self.rate_limiters.handle_update_daily,
|
||||
};
|
||||
|
||||
let ok = limiter.check_key(&client_ip.to_string()).is_ok();
|
||||
|
||||
+1
-1
@@ -430,7 +430,7 @@ async fn create_account_and_login_internal(client: &Client, make_admin: bool) ->
|
||||
if attempt > 0 {
|
||||
tokio::time::sleep(Duration::from_millis(100 * (attempt as u64 + 1))).await;
|
||||
}
|
||||
let handle = format!("user_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("user-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
|
||||
+7
-7
@@ -11,7 +11,7 @@ use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
#[tokio::test]
|
||||
async fn test_create_self_hosted_did_web() {
|
||||
let client = client();
|
||||
let handle = format!("selfweb_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("selfweb-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -98,7 +98,7 @@ async fn test_external_did_web_no_local_doc() {
|
||||
let mock_uri = mock_server.uri();
|
||||
let mock_addr = mock_uri.trim_start_matches("http://");
|
||||
let did = format!("did:web:{}", mock_addr.replace(":", "%3A"));
|
||||
let handle = format!("extweb_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("extweb-{}", uuid::Uuid::new_v4());
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
|
||||
let reserve_res = client
|
||||
@@ -180,7 +180,7 @@ async fn test_external_did_web_no_local_doc() {
|
||||
#[tokio::test]
|
||||
async fn test_plc_operations_blocked_for_did_web() {
|
||||
let client = client();
|
||||
let handle = format!("plcblock_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("plcblock-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -245,7 +245,7 @@ async fn test_plc_operations_blocked_for_did_web() {
|
||||
#[tokio::test]
|
||||
async fn test_get_recommended_did_credentials_no_rotation_keys_for_did_web() {
|
||||
let client = client();
|
||||
let handle = format!("creds_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("creds-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -294,7 +294,7 @@ async fn test_get_recommended_did_credentials_no_rotation_keys_for_did_web() {
|
||||
#[tokio::test]
|
||||
async fn test_did_plc_still_works_with_did_type_param() {
|
||||
let client = client();
|
||||
let handle = format!("plctype_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("plctype-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -323,7 +323,7 @@ async fn test_did_plc_still_works_with_did_type_param() {
|
||||
#[tokio::test]
|
||||
async fn test_external_did_web_requires_did_field() {
|
||||
let client = client();
|
||||
let handle = format!("nodid_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("nodid-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -392,7 +392,7 @@ async fn test_did_web_byod_flow() {
|
||||
mock_addr.replace(":", "%3A"),
|
||||
unique_id
|
||||
);
|
||||
let handle = format!("byod_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("byod-{}", uuid::Uuid::new_v4());
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
let pds_did = format!("did:web:{}", pds_endpoint.trim_start_matches("https://"));
|
||||
|
||||
|
||||
+13
-13
@@ -67,7 +67,7 @@ async fn test_email_update_flow_success() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("emailup_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("new_{}@example.com", handle);
|
||||
@@ -108,10 +108,10 @@ async fn test_email_update_flow_success() {
|
||||
async fn test_request_email_update_taken_email() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle1 = format!("emailup_taken1_{}", uuid::Uuid::new_v4());
|
||||
let handle1 = format!("emailup-taken1-{}", uuid::Uuid::new_v4());
|
||||
let email1 = format!("{}@example.com", handle1);
|
||||
let (_, _) = create_verified_account(&client, &base_url, &handle1, &email1).await;
|
||||
let handle2 = format!("emailup_taken2_{}", uuid::Uuid::new_v4());
|
||||
let handle2 = format!("emailup-taken2-{}", uuid::Uuid::new_v4());
|
||||
let email2 = format!("{}@example.com", handle2);
|
||||
let (access_jwt2, _) = create_verified_account(&client, &base_url, &handle2, &email2).await;
|
||||
let res = client
|
||||
@@ -133,7 +133,7 @@ async fn test_request_email_update_taken_email() {
|
||||
async fn test_confirm_email_invalid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_inv_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-inv-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("new_{}@example.com", handle);
|
||||
@@ -168,7 +168,7 @@ async fn test_confirm_email_wrong_email() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("emailup_wrong_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-wrong-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("new_{}@example.com", handle);
|
||||
@@ -205,7 +205,7 @@ async fn test_confirm_email_wrong_email() {
|
||||
async fn test_update_email_requires_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_direct_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-direct-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("direct_{}@example.com", handle);
|
||||
@@ -225,7 +225,7 @@ async fn test_update_email_requires_token() {
|
||||
async fn test_update_email_same_email_noop() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_same_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-same-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let res = client
|
||||
@@ -246,7 +246,7 @@ async fn test_update_email_same_email_noop() {
|
||||
async fn test_update_email_requires_token_after_pending() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_token_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-token-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("pending_{}@example.com", handle);
|
||||
@@ -278,7 +278,7 @@ async fn test_update_email_with_valid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("emailup_valid_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-valid-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("valid_{}@example.com", handle);
|
||||
@@ -316,7 +316,7 @@ async fn test_update_email_with_valid_token() {
|
||||
async fn test_update_email_invalid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_badtok_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-badtok-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let new_email = format!("badtok_{}@example.com", handle);
|
||||
@@ -350,10 +350,10 @@ async fn test_update_email_invalid_token() {
|
||||
async fn test_update_email_already_taken() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle1 = format!("emailup_dup1_{}", uuid::Uuid::new_v4());
|
||||
let handle1 = format!("emailup-dup1-{}", uuid::Uuid::new_v4());
|
||||
let email1 = format!("{}@example.com", handle1);
|
||||
let (_, _) = create_verified_account(&client, &base_url, &handle1, &email1).await;
|
||||
let handle2 = format!("emailup_dup2_{}", uuid::Uuid::new_v4());
|
||||
let handle2 = format!("emailup-dup2-{}", uuid::Uuid::new_v4());
|
||||
let email2 = format!("{}@example.com", handle2);
|
||||
let (access_jwt2, _) = create_verified_account(&client, &base_url, &handle2, &email2).await;
|
||||
let res = client
|
||||
@@ -394,7 +394,7 @@ async fn test_update_email_no_auth() {
|
||||
async fn test_update_email_invalid_format() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup_fmt_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("emailup-fmt-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
let res = client
|
||||
|
||||
+160
-4
@@ -8,7 +8,7 @@ use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
#[tokio::test]
|
||||
async fn test_resolve_handle_success() {
|
||||
let client = client();
|
||||
let short_handle = format!("resolvetest_{}", uuid::Uuid::new_v4());
|
||||
let short_handle = format!("resolvetest-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({
|
||||
"handle": short_handle,
|
||||
"email": format!("{}@example.com", short_handle),
|
||||
@@ -98,7 +98,7 @@ async fn test_create_did_web_account_and_resolve() {
|
||||
let mock_uri = mock_server.uri();
|
||||
let mock_addr = mock_uri.trim_start_matches("http://");
|
||||
let did = format!("did:web:{}", mock_addr.replace(":", "%3A"));
|
||||
let handle = format!("webuser_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("webuser-{}", uuid::Uuid::new_v4());
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
|
||||
let reserve_res = client
|
||||
@@ -183,7 +183,7 @@ async fn test_create_did_web_account_and_resolve() {
|
||||
#[tokio::test]
|
||||
async fn test_create_account_duplicate_handle() {
|
||||
let client = client();
|
||||
let handle = format!("dupe_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("dupe-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -220,7 +220,7 @@ async fn test_did_web_lifecycle() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let mock_uri = mock_server.uri();
|
||||
let mock_addr = mock_uri.trim_start_matches("http://");
|
||||
let handle = format!("lifecycle_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("lifecycle-{}", uuid::Uuid::new_v4());
|
||||
let did = format!("did:web:{}:u:{}", mock_addr.replace(":", "%3A"), handle);
|
||||
let email = format!("{}@test.com", handle);
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
@@ -378,3 +378,159 @@ async fn test_get_recommended_did_credentials_no_auth() {
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "AuthenticationRequired");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_to_same() {
|
||||
let client = client();
|
||||
let (access_jwt, _did) = create_account_and_login(&client).await;
|
||||
let session = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.server.getSession",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get session");
|
||||
let session_body: Value = session.json().await.expect("Invalid JSON");
|
||||
let current_handle = session_body["handle"].as_str().expect("No handle").to_string();
|
||||
let short_handle = current_handle.split('.').next().unwrap_or(¤t_handle);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({ "handle": short_handle }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_no_auth() {
|
||||
let client = client();
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.json(&json!({ "handle": "newhandle" }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "AuthenticationRequired");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_invalid_characters() {
|
||||
let client = client();
|
||||
let (access_jwt, _did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({ "handle": "invalid@handle!" }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "InvalidHandle");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_empty() {
|
||||
let client = client();
|
||||
let (access_jwt, _did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({ "handle": "" }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "InvalidRequest");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_taken() {
|
||||
let client = client();
|
||||
let (access_jwt1, _did1) = create_account_and_login(&client).await;
|
||||
let (access_jwt2, _did2) = create_account_and_login(&client).await;
|
||||
let short_handle = format!("taken{}", &uuid::Uuid::new_v4().to_string()[..8]);
|
||||
let update1 = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt1)
|
||||
.json(&json!({ "handle": short_handle }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to update handle");
|
||||
assert_eq!(update1.status(), StatusCode::OK);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt2)
|
||||
.json(&json!({ "handle": short_handle }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "HandleTaken");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_too_short() {
|
||||
let client = client();
|
||||
let (access_jwt, _did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({ "handle": "ab" }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "InvalidHandle");
|
||||
assert!(body["message"].as_str().unwrap().contains("short"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_handle_too_long() {
|
||||
let client = client();
|
||||
let (access_jwt, _did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.identity.updateHandle",
|
||||
base_url().await
|
||||
))
|
||||
.bearer_auth(&access_jwt)
|
||||
.json(&json!({ "handle": "thishandleiswaytoolongforservicedomain" }))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = res.json().await.expect("Response was not valid JSON");
|
||||
assert_eq!(body["error"], "InvalidHandle");
|
||||
assert!(body["message"].as_str().unwrap().contains("long"));
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ async fn test_request_password_reset_creates_code() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("pwreset_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pwreset-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -81,7 +81,7 @@ async fn test_reset_password_with_valid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("pwreset2_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pwreset2-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let old_password = "Oldpass123!";
|
||||
let new_password = "Newpass456!";
|
||||
@@ -197,7 +197,7 @@ async fn test_reset_password_with_expired_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("pwreset3_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pwreset3-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -261,7 +261,7 @@ async fn test_reset_password_invalidates_sessions() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = get_pool().await;
|
||||
let handle = format!("pwreset4_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pwreset4-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -351,7 +351,7 @@ async fn test_reset_password_creates_notification() {
|
||||
let pool = get_pool().await;
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("pwreset5_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pwreset5-{}", uuid::Uuid::new_v4());
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ async fn test_login_rate_limiting() {
|
||||
let client = client();
|
||||
let url = format!("{}/xrpc/com.atproto.server.createSession", base_url().await);
|
||||
let payload = json!({
|
||||
"identifier": "nonexistent_user_for_rate_limit_test",
|
||||
"identifier": "nonexistent-user-for-rate-limit-test",
|
||||
"password": "wrongpassword"
|
||||
});
|
||||
let mut rate_limited_count = 0;
|
||||
@@ -53,7 +53,7 @@ async fn test_password_reset_rate_limiting() {
|
||||
let mut success_count = 0;
|
||||
for i in 0..8 {
|
||||
let payload = json!({
|
||||
"email": format!("ratelimit_test_{}@example.com", i)
|
||||
"email": format!("ratelimit-test_{}@example.com", i)
|
||||
});
|
||||
let res = client
|
||||
.post(&url)
|
||||
@@ -91,8 +91,8 @@ async fn test_account_creation_rate_limiting() {
|
||||
for i in 0..15 {
|
||||
let unique_id = uuid::Uuid::new_v4();
|
||||
let payload = json!({
|
||||
"handle": format!("ratelimit_{}_{}", i, unique_id),
|
||||
"email": format!("ratelimit_{}_{}@example.com", i, unique_id),
|
||||
"handle": format!("ratelimit-{}_{}", i, unique_id),
|
||||
"email": format!("ratelimit-{}_{}@example.com", i, unique_id),
|
||||
"password": "Testpass123!"
|
||||
});
|
||||
let res = client
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ async fn test_server_basics() {
|
||||
async fn test_account_and_session_lifecycle() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let handle = format!("user_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("user-{}", uuid::Uuid::new_v4());
|
||||
let payload = json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Testpass123!" });
|
||||
let create_res = client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", base))
|
||||
|
||||
@@ -174,7 +174,7 @@ async fn test_create_account_with_reserved_signing_key() {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body: Value = res.json().await.unwrap();
|
||||
let signing_key = body["signingKey"].as_str().unwrap();
|
||||
let handle = format!("reserved_key_user_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("reserved-key-user-{}", uuid::Uuid::new_v4());
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -212,7 +212,7 @@ async fn test_create_account_with_reserved_signing_key() {
|
||||
async fn test_create_account_with_invalid_signing_key() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("bad_key_user_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("bad-key-user-{}", uuid::Uuid::new_v4());
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -248,7 +248,7 @@ async fn test_create_account_cannot_reuse_signing_key() {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body: Value = res.json().await.unwrap();
|
||||
let signing_key = body["signingKey"].as_str().unwrap();
|
||||
let handle1 = format!("reuse_key_user1_{}", uuid::Uuid::new_v4());
|
||||
let handle1 = format!("reuse-key-user1-{}", uuid::Uuid::new_v4());
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -264,7 +264,7 @@ async fn test_create_account_cannot_reuse_signing_key() {
|
||||
.await
|
||||
.expect("Failed to create first account");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let handle2 = format!("reuse_key_user2_{}", uuid::Uuid::new_v4());
|
||||
let handle2 = format!("reuse-key-user2-{}", uuid::Uuid::new_v4());
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -301,7 +301,7 @@ async fn test_reserved_key_tokens_work() {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body: Value = res.json().await.unwrap();
|
||||
let signing_key = body["signingKey"].as_str().unwrap();
|
||||
let handle = format!("token_test_user_{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("token-test-user-{}", uuid::Uuid::new_v4());
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
|
||||
Reference in New Issue
Block a user