mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-07 10:46:56 +00:00
Reserved handles & misc fixes
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
reference-pds-hailey/
|
||||
reference-pds-bsky/
|
||||
reference-relay-indigo/
|
||||
pds-moover/
|
||||
# Frontend build artifacts
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
|
||||
@@ -5,12 +5,26 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use chrono::{Datelike, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
const APP_BSKY_NAMESPACE: &str = "app.bsky";
|
||||
const MAX_PREFERENCES_COUNT: usize = 100;
|
||||
const MAX_PREFERENCE_SIZE: usize = 10_000;
|
||||
const PERSONAL_DETAILS_PREF: &str = "app.bsky.actor.defs#personalDetailsPref";
|
||||
const DECLARED_AGE_PREF: &str = "app.bsky.actor.defs#declaredAgePref";
|
||||
|
||||
fn get_age_from_datestring(birth_date: &str) -> Option<i32> {
|
||||
let bday = NaiveDate::parse_from_str(birth_date, "%Y-%m-%d").ok()?;
|
||||
let today = Utc::now().date_naive();
|
||||
let mut age = today.year() - bday.year();
|
||||
let m = today.month() as i32 - bday.month() as i32;
|
||||
if m < 0 || (m == 0 && today.day() < bday.day()) {
|
||||
age -= 1;
|
||||
}
|
||||
Some(age)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetPreferencesOutput {
|
||||
@@ -43,6 +57,7 @@ pub async fn get_preferences(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let has_full_access = auth_user.permissions().has_full_access();
|
||||
let user_id: uuid::Uuid =
|
||||
match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", auth_user.did)
|
||||
.fetch_optional(&state.db)
|
||||
@@ -73,19 +88,39 @@ pub async fn get_preferences(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let preferences: Vec<Value> = prefs
|
||||
let mut personal_details_pref: Option<Value> = None;
|
||||
let mut preferences: Vec<Value> = prefs
|
||||
.into_iter()
|
||||
.filter(|row| {
|
||||
row.name == APP_BSKY_NAMESPACE
|
||||
|| row.name.starts_with(&format!("{}.", APP_BSKY_NAMESPACE))
|
||||
})
|
||||
.filter_map(|row| {
|
||||
if row.name == "app.bsky.actor.defs#declaredAgePref" {
|
||||
if row.name == DECLARED_AGE_PREF {
|
||||
return None;
|
||||
}
|
||||
if row.name == PERSONAL_DETAILS_PREF {
|
||||
if !has_full_access {
|
||||
return None;
|
||||
}
|
||||
personal_details_pref = serde_json::from_value(row.value_json.clone()).ok();
|
||||
}
|
||||
serde_json::from_value(row.value_json).ok()
|
||||
})
|
||||
.collect();
|
||||
if let Some(ref pref) = personal_details_pref {
|
||||
if let Some(birth_date) = pref.get("birthDate").and_then(|v| v.as_str()) {
|
||||
if let Some(age) = get_age_from_datestring(birth_date) {
|
||||
let declared_age_pref = json!({
|
||||
"$type": DECLARED_AGE_PREF,
|
||||
"isOverAge13": age >= 13,
|
||||
"isOverAge16": age >= 16,
|
||||
"isOverAge18": age >= 18,
|
||||
});
|
||||
preferences.push(declared_age_pref);
|
||||
}
|
||||
}
|
||||
}
|
||||
(StatusCode::OK, Json(GetPreferencesOutput { preferences })).into_response()
|
||||
}
|
||||
|
||||
@@ -121,14 +156,15 @@ pub async fn put_preferences(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let (user_id, is_migration): (uuid::Uuid, bool) = match sqlx::query!(
|
||||
"SELECT id, deactivated_at FROM users WHERE did = $1",
|
||||
let has_full_access = auth_user.permissions().has_full_access();
|
||||
let user_id: uuid::Uuid = match sqlx::query_scalar!(
|
||||
"SELECT id FROM users WHERE did = $1",
|
||||
auth_user.did
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(Some(row)) => (row.id, row.deactivated_at.is_some()),
|
||||
Ok(Some(id)) => id,
|
||||
_ => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -144,6 +180,7 @@ pub async fn put_preferences(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let mut forbidden_prefs: Vec<String> = Vec::new();
|
||||
for pref in &input.preferences {
|
||||
let pref_str = serde_json::to_string(pref).unwrap_or_default();
|
||||
if pref_str.len() > MAX_PREFERENCE_SIZE {
|
||||
@@ -158,7 +195,7 @@ pub async fn put_preferences(
|
||||
None => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidRequest", "message": "Preference missing $type field"})),
|
||||
Json(json!({"error": "InvalidRequest", "message": "Preference is missing a $type"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
@@ -166,18 +203,21 @@ pub async fn put_preferences(
|
||||
if !pref_type.starts_with(APP_BSKY_NAMESPACE) {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidRequest", "message": format!("Invalid preference namespace: {}", pref_type)})),
|
||||
Json(json!({"error": "InvalidRequest", "message": format!("Some preferences are not in the {} namespace", APP_BSKY_NAMESPACE)})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if pref_type == "app.bsky.actor.defs#declaredAgePref" && !is_migration {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidRequest", "message": "declaredAgePref is read-only"})),
|
||||
)
|
||||
.into_response();
|
||||
if pref_type == PERSONAL_DETAILS_PREF && !has_full_access {
|
||||
forbidden_prefs.push(pref_type.to_string());
|
||||
}
|
||||
}
|
||||
if !forbidden_prefs.is_empty() {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "InvalidRequest", "message": format!("Do not have authorization to set preferences: {}", forbidden_prefs.join(", "))})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let mut tx = match state.db.begin().await {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => {
|
||||
@@ -209,6 +249,9 @@ pub async fn put_preferences(
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
if pref_type == DECLARED_AGE_PREF {
|
||||
continue;
|
||||
}
|
||||
let insert_result = sqlx::query!(
|
||||
"INSERT INTO account_preferences (user_id, name, value_json) VALUES ($1, $2, $3)",
|
||||
user_id,
|
||||
|
||||
@@ -188,6 +188,13 @@ pub async fn create_account(
|
||||
};
|
||||
match crate::api::validation::validate_short_handle(handle_to_validate) {
|
||||
Ok(h) => h,
|
||||
Err(crate::api::validation::HandleValidationError::Reserved) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "HandleNotAvailable", "message": "Reserved handle"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
Err(e) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
|
||||
@@ -10,6 +10,29 @@ use axum::{
|
||||
use serde_json::json;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
const PROTECTED_METHODS: &[&str] = &[
|
||||
"com.atproto.admin.sendEmail",
|
||||
"com.atproto.identity.requestPlcOperationSignature",
|
||||
"com.atproto.identity.signPlcOperation",
|
||||
"com.atproto.identity.updateHandle",
|
||||
"com.atproto.server.activateAccount",
|
||||
"com.atproto.server.confirmEmail",
|
||||
"com.atproto.server.createAppPassword",
|
||||
"com.atproto.server.deactivateAccount",
|
||||
"com.atproto.server.getAccountInviteCodes",
|
||||
"com.atproto.server.getSession",
|
||||
"com.atproto.server.listAppPasswords",
|
||||
"com.atproto.server.requestAccountDelete",
|
||||
"com.atproto.server.requestEmailConfirmation",
|
||||
"com.atproto.server.requestEmailUpdate",
|
||||
"com.atproto.server.revokeAppPassword",
|
||||
"com.atproto.server.updateEmail",
|
||||
];
|
||||
|
||||
fn is_protected_method(method: &str) -> bool {
|
||||
PROTECTED_METHODS.contains(&method)
|
||||
}
|
||||
|
||||
pub async fn proxy_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(method): Path<String>,
|
||||
@@ -18,6 +41,18 @@ pub async fn proxy_handler(
|
||||
RawQuery(query): RawQuery,
|
||||
body: Bytes,
|
||||
) -> Response {
|
||||
if is_protected_method(&method) {
|
||||
warn!(method = %method, "Attempted to proxy protected method");
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": "InvalidRequest",
|
||||
"message": format!("Cannot proxy protected method: {}", method)
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let proxy_header = match headers.get("atproto-proxy").and_then(|h| h.to_str().ok()) {
|
||||
Some(h) => h.to_string(),
|
||||
None => {
|
||||
|
||||
+78
-2
@@ -6,6 +6,7 @@ const EMAIL_LOCAL_SPECIAL_CHARS: &str = ".!#$%&'*+/=?^_`{|}~-";
|
||||
|
||||
pub const MIN_HANDLE_LENGTH: usize = 3;
|
||||
pub const MAX_HANDLE_LENGTH: usize = 253;
|
||||
pub const MAX_SERVICE_HANDLE_LOCAL_PART: usize = 18;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum HandleValidationError {
|
||||
@@ -17,6 +18,7 @@ pub enum HandleValidationError {
|
||||
EndsWithInvalidChar,
|
||||
ContainsSpaces,
|
||||
BannedWord,
|
||||
Reserved,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HandleValidationError {
|
||||
@@ -31,7 +33,7 @@ impl std::fmt::Display for HandleValidationError {
|
||||
Self::TooLong => write!(
|
||||
f,
|
||||
"Handle exceeds maximum length of {} characters",
|
||||
MAX_HANDLE_LENGTH
|
||||
MAX_SERVICE_HANDLE_LOCAL_PART
|
||||
),
|
||||
Self::InvalidCharacters => write!(
|
||||
f,
|
||||
@@ -43,11 +45,19 @@ impl std::fmt::Display for HandleValidationError {
|
||||
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"),
|
||||
Self::Reserved => write!(f, "Reserved handle"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationError> {
|
||||
validate_service_handle(handle, false)
|
||||
}
|
||||
|
||||
pub fn validate_service_handle(
|
||||
handle: &str,
|
||||
allow_reserved: bool,
|
||||
) -> Result<String, HandleValidationError> {
|
||||
let handle = handle.trim();
|
||||
|
||||
if handle.is_empty() {
|
||||
@@ -62,7 +72,7 @@ pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationErr
|
||||
return Err(HandleValidationError::TooShort);
|
||||
}
|
||||
|
||||
if handle.len() > MAX_HANDLE_LENGTH {
|
||||
if handle.len() > MAX_SERVICE_HANDLE_LOCAL_PART {
|
||||
return Err(HandleValidationError::TooLong);
|
||||
}
|
||||
|
||||
@@ -88,6 +98,10 @@ pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationErr
|
||||
return Err(HandleValidationError::BannedWord);
|
||||
}
|
||||
|
||||
if !allow_reserved && crate::handle::reserved::is_reserved_subdomain(handle) {
|
||||
return Err(HandleValidationError::Reserved);
|
||||
}
|
||||
|
||||
Ok(handle.to_lowercase())
|
||||
}
|
||||
|
||||
@@ -223,6 +237,68 @@ mod tests {
|
||||
assert_eq!(validate_short_handle(" alice "), Ok("alice".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_max_length() {
|
||||
assert_eq!(
|
||||
validate_short_handle("exactly18charslol"),
|
||||
Ok("exactly18charslol".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("exactly18charslol1"),
|
||||
Ok("exactly18charslol1".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("exactly19characters"),
|
||||
Err(HandleValidationError::TooLong)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("waytoolongusername123456789"),
|
||||
Err(HandleValidationError::TooLong)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reserved_subdomains() {
|
||||
assert_eq!(
|
||||
validate_short_handle("admin"),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("api"),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("bsky"),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("barackobama"),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_short_handle("ADMIN"),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
|
||||
assert_eq!(
|
||||
validate_short_handle("notreserved"),
|
||||
Ok("notreserved".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_allow_reserved() {
|
||||
assert_eq!(
|
||||
validate_service_handle("admin", true),
|
||||
Ok("admin".to_string())
|
||||
);
|
||||
assert_eq!(validate_service_handle("api", true), Ok("api".to_string()));
|
||||
assert_eq!(
|
||||
validate_service_handle("admin", false),
|
||||
Err(HandleValidationError::Reserved)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_emails() {
|
||||
assert!(is_valid_email("user@example.com"));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod reserved;
|
||||
|
||||
use hickory_resolver::TokioAsyncResolver;
|
||||
use hickory_resolver::config::{ResolverConfig, ResolverOpts};
|
||||
use reqwest::Client;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,7 @@ pub async fn oauth_authorization_server(
|
||||
"atproto".to_string(),
|
||||
"transition:generic".to_string(),
|
||||
"transition:chat.bsky".to_string(),
|
||||
"transition:email".to_string(),
|
||||
"repo:*".to_string(),
|
||||
"repo:*?action=create".to_string(),
|
||||
"repo:*?action=read".to_string(),
|
||||
|
||||
+39
-62
@@ -1,9 +1,11 @@
|
||||
use crate::auth::{extract_bearer_token_from_header, validate_bearer_token_allow_takendown};
|
||||
use crate::state::AppState;
|
||||
use crate::sync::car::encode_car_header;
|
||||
use crate::sync::util::assert_repo_availability;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use cid::Cid;
|
||||
@@ -13,10 +15,22 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use tracing::error;
|
||||
|
||||
const MAX_REPO_BLOCKS_TRAVERSAL: usize = 20_000;
|
||||
|
||||
async fn check_admin_or_self(state: &AppState, headers: &HeaderMap, did: &str) -> bool {
|
||||
let token = match extract_bearer_token_from_header(
|
||||
headers.get("Authorization").and_then(|h| h.to_str().ok()),
|
||||
) {
|
||||
Some(t) => t,
|
||||
None => return false,
|
||||
};
|
||||
match validate_bearer_token_allow_takendown(&state.db, &token).await {
|
||||
Ok(auth_user) => auth_user.is_admin || auth_user.did == did,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetHeadParams {
|
||||
pub did: String,
|
||||
@@ -29,6 +43,7 @@ pub struct GetHeadOutput {
|
||||
|
||||
pub async fn get_head(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Query(params): Query<GetHeadParams>,
|
||||
) -> Response {
|
||||
let did = params.did.trim();
|
||||
@@ -39,38 +54,18 @@ pub async fn get_head(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT r.repo_root_cid
|
||||
FROM repos r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
WHERE u.did = $1
|
||||
"#,
|
||||
did
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
.await;
|
||||
match result {
|
||||
Ok(Some(row)) => (
|
||||
StatusCode::OK,
|
||||
Json(GetHeadOutput {
|
||||
root: row.repo_root_cid,
|
||||
}),
|
||||
)
|
||||
.into_response(),
|
||||
Ok(None) => (
|
||||
let is_admin_or_self = check_admin_or_self(&state, &headers, did).await;
|
||||
let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => return e.into_response(),
|
||||
};
|
||||
match account.repo_root_cid {
|
||||
Some(root) => (StatusCode::OK, Json(GetHeadOutput { root })).into_response(),
|
||||
None => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "HeadNotFound", "message": "Could not find root for DID"})),
|
||||
Json(json!({"error": "HeadNotFound", "message": format!("Could not find root for DID: {}", did)})),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
error!("DB error in get_head: {:?}", e);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +76,7 @@ pub struct GetCheckoutParams {
|
||||
|
||||
pub async fn get_checkout(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Query(params): Query<GetCheckoutParams>,
|
||||
) -> Response {
|
||||
let did = params.did.trim();
|
||||
@@ -91,38 +87,19 @@ pub async fn get_checkout(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let repo_row = sqlx::query!(
|
||||
r#"
|
||||
SELECT r.repo_root_cid
|
||||
FROM repos r
|
||||
JOIN users u ON u.id = r.user_id
|
||||
WHERE u.did = $1
|
||||
"#,
|
||||
did
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.unwrap_or(None);
|
||||
let head_str = match repo_row {
|
||||
Some(r) => r.repo_root_cid,
|
||||
let is_admin_or_self = check_admin_or_self(&state, &headers, did).await;
|
||||
let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => return e.into_response(),
|
||||
};
|
||||
let head_str = match account.repo_root_cid {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
let user_exists = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.unwrap_or(None);
|
||||
if user_exists.is_none() {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(json!({"error": "RepoNotFound", "message": "Repo not found"})),
|
||||
)
|
||||
.into_response();
|
||||
} else {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(json!({"error": "RepoNotFound", "message": "Repo not initialized"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": "RepoNotFound", "message": "Repo not initialized"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let head_cid = match Cid::from_str(&head_str) {
|
||||
|
||||
@@ -154,7 +154,7 @@ async fn test_create_account_returns_did_doc() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
|
||||
let handle = format!("diddoctest-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("dd{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -185,7 +185,7 @@ async fn test_create_account_always_returns_tokens() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
|
||||
let handle = format!("tokentest-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("tt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
@@ -243,7 +243,7 @@ async fn test_delete_account_password_max_length() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
|
||||
let handle = format!("pwdlentest-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pl{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
"email": format!("{}@example.com", handle),
|
||||
|
||||
+105
-4
@@ -140,7 +140,7 @@ async fn test_put_preferences_invalid_namespace() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_preferences_read_only_rejected() {
|
||||
async fn test_put_preferences_read_only_silently_filtered() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (token, _did) = create_account_and_login(&client).await;
|
||||
@@ -149,6 +149,10 @@ async fn test_put_preferences_read_only_rejected() {
|
||||
{
|
||||
"$type": "app.bsky.actor.defs#declaredAgePref",
|
||||
"isOverAge18": true
|
||||
},
|
||||
{
|
||||
"$type": "app.bsky.actor.defs#adultContentPref",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -159,9 +163,18 @@ async fn test_put_preferences_read_only_rejected() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 400);
|
||||
let body: Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["error"], "InvalidRequest");
|
||||
assert_eq!(resp.status(), 200);
|
||||
let get_resp = client
|
||||
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(get_resp.status(), 200);
|
||||
let body: Value = get_resp.json().await.unwrap();
|
||||
let prefs_arr = body["preferences"].as_array().unwrap();
|
||||
assert_eq!(prefs_arr.len(), 1);
|
||||
assert_eq!(prefs_arr[0]["$type"], "app.bsky.actor.defs#adultContentPref");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -328,3 +341,91 @@ async fn test_preferences_isolation_between_users() {
|
||||
let body: Value = resp.json().await.unwrap();
|
||||
assert!(body["preferences"].as_array().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_declared_age_pref_computed_from_birth_date() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (token, _did) = create_account_and_login(&client).await;
|
||||
let prefs = json!({
|
||||
"preferences": [
|
||||
{
|
||||
"$type": "app.bsky.actor.defs#personalDetailsPref",
|
||||
"birthDate": "1990-01-15"
|
||||
}
|
||||
]
|
||||
});
|
||||
let resp = client
|
||||
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.json(&prefs)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let get_resp = client
|
||||
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(get_resp.status(), 200);
|
||||
let body: Value = get_resp.json().await.unwrap();
|
||||
let prefs_arr = body["preferences"].as_array().unwrap();
|
||||
assert_eq!(prefs_arr.len(), 2);
|
||||
let personal_details = prefs_arr
|
||||
.iter()
|
||||
.find(|p| p["$type"] == "app.bsky.actor.defs#personalDetailsPref");
|
||||
assert!(personal_details.is_some());
|
||||
assert_eq!(personal_details.unwrap()["birthDate"], "1990-01-15");
|
||||
let declared_age = prefs_arr
|
||||
.iter()
|
||||
.find(|p| p["$type"] == "app.bsky.actor.defs#declaredAgePref");
|
||||
assert!(declared_age.is_some());
|
||||
let declared_age = declared_age.unwrap();
|
||||
assert_eq!(declared_age["isOverAge13"], true);
|
||||
assert_eq!(declared_age["isOverAge16"], true);
|
||||
assert_eq!(declared_age["isOverAge18"], true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_declared_age_pref_computed_under_18() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (token, _did) = create_account_and_login(&client).await;
|
||||
let current_year = chrono::Utc::now().format("%Y").to_string().parse::<i32>().unwrap();
|
||||
let birth_year = current_year - 15;
|
||||
let prefs = json!({
|
||||
"preferences": [
|
||||
{
|
||||
"$type": "app.bsky.actor.defs#personalDetailsPref",
|
||||
"birthDate": format!("{}-06-15", birth_year)
|
||||
}
|
||||
]
|
||||
});
|
||||
let resp = client
|
||||
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.json(&prefs)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let get_resp = client
|
||||
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(get_resp.status(), 200);
|
||||
let body: Value = get_resp.json().await.unwrap();
|
||||
let prefs_arr = body["preferences"].as_array().unwrap();
|
||||
let declared_age = prefs_arr
|
||||
.iter()
|
||||
.find(|p| p["$type"] == "app.bsky.actor.defs#declaredAgePref");
|
||||
assert!(declared_age.is_some());
|
||||
let declared_age = declared_age.unwrap();
|
||||
assert_eq!(declared_age["isOverAge13"], true);
|
||||
assert_eq!(declared_age["isOverAge16"], false);
|
||||
assert_eq!(declared_age["isOverAge18"], false);
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ fn decode(b64: &str) -> String {
|
||||
#[tokio::test]
|
||||
async fn test_handle_with_slur_rejected() {
|
||||
let client = client();
|
||||
let timestamp = chrono::Utc::now().timestamp_millis();
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let slur = decode("bmlnZ2Vy");
|
||||
let offensive_handle = format!("{}{}", slur, timestamp);
|
||||
let offensive_handle = format!("{}{}", slur, suffix);
|
||||
|
||||
let create_payload = json!({
|
||||
"handle": offensive_handle,
|
||||
"email": format!("test{}@example.com", timestamp),
|
||||
"email": format!("test{}@example.com", suffix),
|
||||
"password": "TestPassword123!"
|
||||
});
|
||||
|
||||
@@ -65,13 +65,13 @@ async fn test_handle_with_slur_rejected() {
|
||||
#[tokio::test]
|
||||
async fn test_handle_with_normalized_slur_rejected() {
|
||||
let client = client();
|
||||
let timestamp = chrono::Utc::now().timestamp_millis();
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..6];
|
||||
let slur = decode("bi1pLWctZy1lLXI=");
|
||||
let offensive_handle = format!("{}{}", slur, timestamp);
|
||||
let offensive_handle = format!("{}{}", slur, suffix);
|
||||
|
||||
let create_payload = json!({
|
||||
"handle": offensive_handle,
|
||||
"email": format!("test{}@example.com", timestamp),
|
||||
"email": format!("test{}@example.com", suffix),
|
||||
"password": "TestPassword123!"
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -440,7 +440,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!("u{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("sw{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("xw{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("pb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("cr{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("pt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("nd{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("by{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let pds_endpoint = base_url().await.replace("http://", "https://");
|
||||
let pds_did = format!("did:web:{}", pds_endpoint.trim_start_matches("https://"));
|
||||
|
||||
|
||||
+12
-12
@@ -57,7 +57,7 @@ async fn create_verified_account(
|
||||
async fn test_request_email_update_returns_token_required() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailreq-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("er{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
|
||||
@@ -80,7 +80,7 @@ async fn test_update_email_flow_success() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("emailup-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("eu{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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);
|
||||
@@ -123,7 +123,7 @@ async fn test_update_email_flow_success() {
|
||||
async fn test_update_email_requires_token_when_verified() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailup-direct-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("ed{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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);
|
||||
@@ -144,7 +144,7 @@ async fn test_update_email_requires_token_when_verified() {
|
||||
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!("es{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
|
||||
@@ -166,7 +166,7 @@ async fn test_update_email_same_email_noop() {
|
||||
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!("eb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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);
|
||||
@@ -217,7 +217,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!("ef{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
|
||||
|
||||
@@ -236,7 +236,7 @@ async fn test_confirm_email_confirms_existing_email() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("emailconfirm-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("ec{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
|
||||
let res = client
|
||||
@@ -298,7 +298,7 @@ async fn test_confirm_email_rejects_wrong_email() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("emailconf-wrong-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("ew{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
|
||||
let res = client
|
||||
@@ -352,7 +352,7 @@ async fn test_confirm_email_rejects_wrong_email() {
|
||||
async fn test_confirm_email_invalid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("emailconf-inv-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("ei{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
|
||||
let res = client
|
||||
@@ -392,7 +392,7 @@ async fn test_unverified_account_can_update_email_without_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("emailup-unverified-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("ev{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
|
||||
let res = client
|
||||
@@ -457,11 +457,11 @@ async fn test_update_email_taken_by_another_user() {
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
|
||||
let handle1 = format!("emailup-dup1-{}", uuid::Uuid::new_v4());
|
||||
let handle1 = format!("d1{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("d2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email2 = format!("{}@example.com", handle2);
|
||||
let (access_jwt2, did2) = create_verified_account(&client, &base_url, &handle2, &email2).await;
|
||||
|
||||
|
||||
+4
-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!("rt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("wu{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("dp{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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!("lc{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
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://");
|
||||
|
||||
@@ -669,9 +669,9 @@ async fn test_deactivated_account_behavior() {
|
||||
async fn test_refresh_token_replay_protection() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("rt-replay-jwt-{}", ts);
|
||||
let email = format!("rt-replay-jwt-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("rr{}", suffix);
|
||||
let email = format!("rr{}@example.com", suffix);
|
||||
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
|
||||
+22
-22
@@ -191,9 +191,9 @@ async fn test_par_and_authorize() {
|
||||
async fn test_full_oauth_flow() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("oauth-test-{}", ts);
|
||||
let email = format!("oauth-test-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("ot{}", suffix);
|
||||
let email = format!("ot{}@example.com", suffix);
|
||||
let password = "Oauthtest123!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -209,7 +209,7 @@ async fn test_full_oauth_flow() {
|
||||
let mock_client = setup_mock_client_metadata(redirect_uri).await;
|
||||
let client_id = mock_client.uri();
|
||||
let (code_verifier, code_challenge) = generate_pkce();
|
||||
let state = format!("state-{}", ts);
|
||||
let state = format!("state-{}", suffix);
|
||||
let par_res = http_client
|
||||
.post(format!("{}/oauth/par", url))
|
||||
.form(&[
|
||||
@@ -349,9 +349,9 @@ async fn test_full_oauth_flow() {
|
||||
async fn test_oauth_error_cases() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("wrong-creds-{}", ts);
|
||||
let email = format!("wrong-creds-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("wc{}", suffix);
|
||||
let email = format!("wc{}@example.com", suffix);
|
||||
http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle, "email": email, "password": "Correct123!" }))
|
||||
@@ -435,9 +435,9 @@ async fn test_oauth_error_cases() {
|
||||
async fn test_oauth_2fa_flow() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("2fa-test-{}", ts);
|
||||
let email = format!("2fa-test-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("ft{}", suffix);
|
||||
let email = format!("ft{}@example.com", suffix);
|
||||
let password = "Twofa123test!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -557,9 +557,9 @@ async fn test_oauth_2fa_flow() {
|
||||
async fn test_oauth_2fa_lockout() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("2fa-lockout-{}", ts);
|
||||
let email = format!("2fa-lockout-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("fl{}", suffix);
|
||||
let email = format!("fl{}@example.com", suffix);
|
||||
let password = "Twofa123test!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -649,9 +649,9 @@ async fn test_oauth_2fa_lockout() {
|
||||
async fn test_account_selector_with_2fa() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("selector-2fa-{}", ts);
|
||||
let email = format!("selector-2fa-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("sf{}", suffix);
|
||||
let email = format!("sf{}@example.com", suffix);
|
||||
let password = "Selector2fa123!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -835,9 +835,9 @@ async fn test_account_selector_with_2fa() {
|
||||
async fn test_oauth_state_encoding() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("state-special-{}", ts);
|
||||
let email = format!("state-special-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("ss{}", suffix);
|
||||
let email = format!("ss{}@example.com", suffix);
|
||||
let password = "State123special!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -914,9 +914,9 @@ async fn test_oauth_state_encoding() {
|
||||
async fn get_oauth_token_with_scope(scope: &str) -> (String, String, String) {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("scope-test-{}", ts);
|
||||
let email = format!("scope-test-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("st{}", suffix);
|
||||
let email = format!("st{}@example.com", suffix);
|
||||
let password = "Scopetest123!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
|
||||
+10
-10
@@ -54,9 +54,9 @@ async fn create_user_and_oauth_session(
|
||||
) -> (OAuthSession, MockServer) {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("{}-{}", handle_prefix, ts);
|
||||
let email = format!("{}-{}@example.com", handle_prefix, ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..4];
|
||||
let handle = format!("{}{}", handle_prefix, suffix);
|
||||
let email = format!("{}{}@example.com", handle_prefix, suffix);
|
||||
let password = format!("{}Pass123!", handle_prefix);
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -269,7 +269,7 @@ async fn test_oauth_full_post_lifecycle_create_edit_delete() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let (session, _mock) =
|
||||
create_user_and_oauth_session("oauth-lifecycle", "https://example.com/callback").await;
|
||||
create_user_and_oauth_session("oauthlife", "https://example.com/callback").await;
|
||||
let collection = "app.bsky.feed.post";
|
||||
let original_text = "Original post content";
|
||||
let create_res = http_client
|
||||
@@ -439,7 +439,7 @@ async fn test_oauth_token_refresh_maintains_access() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let (session, _mock) =
|
||||
create_user_and_oauth_session("oauth-refresh-access", "https://example.com/callback").await;
|
||||
create_user_and_oauth_session("oauth-refr", "https://example.com/callback").await;
|
||||
let collection = "app.bsky.feed.post";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.repo.createRecord", url))
|
||||
@@ -520,7 +520,7 @@ async fn test_oauth_revoked_token_cannot_access_resources() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let (session, _mock) =
|
||||
create_user_and_oauth_session("oauth-revoke-access", "https://example.com/callback").await;
|
||||
create_user_and_oauth_session("oauth-revo", "https://example.com/callback").await;
|
||||
let collection = "app.bsky.feed.post";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.repo.createRecord", url))
|
||||
@@ -574,9 +574,9 @@ async fn test_oauth_revoked_token_cannot_access_resources() {
|
||||
async fn test_oauth_multiple_clients_same_user() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("multi-client-{}", ts);
|
||||
let email = format!("multi-client-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("mc{}", suffix);
|
||||
let email = format!("mc{}@example.com", suffix);
|
||||
let password = "MultiClient123!";
|
||||
let create_res = http_client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
@@ -949,7 +949,7 @@ async fn test_oauth_session_isolation_between_users() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let (alice, _mock_alice) =
|
||||
create_user_and_oauth_session("alice-isolation", "https://alice.example.com/callback")
|
||||
create_user_and_oauth_session("alice-isol", "https://alice.example.com/callback")
|
||||
.await;
|
||||
let (bob, _mock_bob) =
|
||||
create_user_and_oauth_session("bob-isolation", "https://bob.example.com/callback").await;
|
||||
|
||||
+13
-13
@@ -58,9 +58,9 @@ async fn create_user_and_oauth_session_with_scope(
|
||||
) -> (OAuthSession, MockServer) {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("{}-{}", handle_prefix, ts);
|
||||
let email = format!("{}-{}@example.com", handle_prefix, ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..4];
|
||||
let handle = format!("{}{}", handle_prefix, suffix);
|
||||
let email = format!("{}{}@example.com", handle_prefix, suffix);
|
||||
let password = format!("{}Pass123!", handle_prefix);
|
||||
|
||||
let create_res = http_client
|
||||
@@ -345,7 +345,7 @@ async fn test_transition_generic_scope_allows_access() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let (session, _mock) = create_user_and_oauth_session_with_scope(
|
||||
"scope-transition",
|
||||
"scope-trans",
|
||||
"https://example.com/callback",
|
||||
"atproto transition:generic",
|
||||
)
|
||||
@@ -380,9 +380,9 @@ async fn test_consent_endpoint_returns_scope_info() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("consent-test-{}", ts);
|
||||
let email = format!("consent-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("ct{}", suffix);
|
||||
let email = format!("ct{}@example.com", suffix);
|
||||
let password = "Consent123!";
|
||||
let redirect_uri = "https://consent-test.example.com/callback";
|
||||
|
||||
@@ -476,9 +476,9 @@ async fn test_consent_post_generates_code() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("consent-post-{}", ts);
|
||||
let email = format!("consent-post-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("cp{}", suffix);
|
||||
let email = format!("cp{}@example.com", suffix);
|
||||
let password = "ConsentPost123!";
|
||||
let redirect_uri = "https://consent-post.example.com/callback";
|
||||
|
||||
@@ -590,9 +590,9 @@ async fn test_consent_post_requires_atproto_scope() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("consent-req-{}", ts);
|
||||
let email = format!("consent-req-{}@example.com", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("cq{}", suffix);
|
||||
let email = format!("cq{}@example.com", suffix);
|
||||
let password = "ConsentReq123!";
|
||||
let redirect_uri = "https://consent-req.example.com/callback";
|
||||
|
||||
|
||||
+12
-12
@@ -41,8 +41,8 @@ async fn setup_mock_client_metadata(redirect_uri: &str) -> MockServer {
|
||||
}
|
||||
|
||||
async fn get_oauth_tokens(http_client: &reqwest::Client, url: &str) -> (String, String, String) {
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("sec-test-{}", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("se{}", suffix);
|
||||
let create_res = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Security123!" }))
|
||||
.send().await.unwrap();
|
||||
@@ -255,8 +255,8 @@ async fn test_pkce_security() {
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Missing PKCE challenge should be rejected"
|
||||
);
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("pkce-attack-{}", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("pa{}", suffix);
|
||||
let create_res = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Pkce123pass!" }))
|
||||
.send().await.unwrap();
|
||||
@@ -326,8 +326,8 @@ async fn test_pkce_security() {
|
||||
async fn test_replay_attacks() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("replay-{}", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("rp{}", suffix);
|
||||
let create_res = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Replay123pass!" }))
|
||||
.send().await.unwrap();
|
||||
@@ -532,8 +532,8 @@ async fn test_oauth_security_boundaries() {
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Unregistered redirect_uri should be rejected"
|
||||
);
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let handle = format!("deact-{}", ts);
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle = format!("da{}", suffix);
|
||||
let create_res = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Deact123pass!" }))
|
||||
.send().await.unwrap();
|
||||
@@ -576,8 +576,8 @@ async fn test_oauth_security_boundaries() {
|
||||
let client_id_a = mock_a.uri();
|
||||
let mock_b = setup_mock_client_metadata("https://app-b.com/callback").await;
|
||||
let client_id_b = mock_b.uri();
|
||||
let ts2 = Utc::now().timestamp_millis();
|
||||
let handle2 = format!("cross-{}", ts2);
|
||||
let suffix2 = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
let handle2 = format!("cr{}", suffix2);
|
||||
let create_res2 = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url))
|
||||
.json(&json!({ "handle": handle2, "email": format!("{}@example.com", handle2), "password": "Cross123pass!" }))
|
||||
.send().await.unwrap();
|
||||
@@ -1110,11 +1110,11 @@ fn test_dpop_http_method_case() {
|
||||
async fn test_delegation_viewer_scope_cannot_write() {
|
||||
let url = base_url().await;
|
||||
let http_client = client();
|
||||
let ts = Utc::now().timestamp_millis();
|
||||
let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
|
||||
|
||||
let (controller_jwt, controller_did) = create_account_and_login(&http_client).await;
|
||||
|
||||
let delegated_handle = format!("deleg-{}", ts);
|
||||
let delegated_handle = format!("dg{}", suffix);
|
||||
let delegated_res = http_client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.tranquil.delegation.createDelegatedAccount",
|
||||
|
||||
@@ -9,7 +9,7 @@ async fn test_request_password_reset_creates_code() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("pwreset-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pr{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -71,7 +71,7 @@ async fn test_reset_password_with_valid_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("pwreset2-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pr2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let old_password = "Oldpass123!";
|
||||
let new_password = "Newpass456!";
|
||||
@@ -187,7 +187,7 @@ async fn test_reset_password_with_expired_token() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("pwreset3-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pr3{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -251,7 +251,7 @@ async fn test_reset_password_invalidates_sessions() {
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let handle = format!("pwreset4-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pr4{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
@@ -341,7 +341,7 @@ async fn test_reset_password_creates_notification() {
|
||||
let pool = common::get_test_db_pool().await;
|
||||
let client = common::client();
|
||||
let base_url = common::base_url().await;
|
||||
let handle = format!("pwreset5-{}", uuid::Uuid::new_v4());
|
||||
let handle = format!("pr5{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let email = format!("{}@example.com", handle);
|
||||
let payload = json!({
|
||||
"handle": handle,
|
||||
|
||||
+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!("u{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let payload = json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Testpass123!" });
|
||||
let create_res = client
|
||||
.post(format!("{}/xrpc/com.atproto.server.createAccount", base))
|
||||
|
||||
@@ -164,7 +164,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!("rk{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -202,7 +202,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!("bk{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -238,7 +238,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!("r1{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -254,7 +254,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!("r2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
@@ -291,7 +291,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!("tu{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/xrpc/com.atproto.server.createAccount",
|
||||
|
||||
+165
-2
@@ -62,7 +62,7 @@ async fn test_get_head_comprehensive() {
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
|
||||
let error_body: Value = not_found_res.json().await.unwrap();
|
||||
assert_eq!(error_body["error"], "HeadNotFound");
|
||||
assert_eq!(error_body["error"], "RepoNotFound");
|
||||
let missing_res = client
|
||||
.get(format!(
|
||||
"{}/xrpc/com.atproto.sync.getHead",
|
||||
@@ -165,7 +165,7 @@ async fn test_get_checkout_comprehensive() {
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(not_found_res.status(), StatusCode::NOT_FOUND);
|
||||
assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
|
||||
let error_body: Value = not_found_res.json().await.unwrap();
|
||||
assert_eq!(error_body["error"], "RepoNotFound");
|
||||
let missing_res = client
|
||||
@@ -188,3 +188,166 @@ async fn test_get_checkout_comprehensive() {
|
||||
.expect("Failed to send request");
|
||||
assert_eq!(empty_did_res.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_head_deactivated_account_returns_error() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (did, jwt) = setup_new_user("deactheadtest").await;
|
||||
let res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getHead", base))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
client
|
||||
.post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&serde_json::json!({}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let deact_res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getHead", base))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = deact_res.json().await.unwrap();
|
||||
assert_eq!(body["error"], "RepoDeactivated");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_head_takendown_account_returns_error() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (admin_jwt, _) = create_admin_account_and_login(&client).await;
|
||||
let (_, target_did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getHead", base))
|
||||
.query(&[("did", target_did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
client
|
||||
.post(format!("{}/xrpc/com.atproto.admin.updateSubjectStatus", base))
|
||||
.bearer_auth(&admin_jwt)
|
||||
.json(&serde_json::json!({
|
||||
"subject": {
|
||||
"$type": "com.atproto.admin.defs#repoRef",
|
||||
"did": target_did
|
||||
},
|
||||
"takedown": {
|
||||
"applied": true,
|
||||
"ref": "test-takedown"
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let takedown_res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getHead", base))
|
||||
.query(&[("did", target_did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = takedown_res.json().await.unwrap();
|
||||
assert_eq!(body["error"], "RepoTakendown");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_head_admin_can_access_deactivated() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (admin_jwt, _) = create_admin_account_and_login(&client).await;
|
||||
let (user_jwt, did) = create_account_and_login(&client).await;
|
||||
client
|
||||
.post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
|
||||
.bearer_auth(&user_jwt)
|
||||
.json(&serde_json::json!({}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getHead", base))
|
||||
.bearer_auth(&admin_jwt)
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_checkout_deactivated_account_returns_error() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (did, jwt) = setup_new_user("deactcheckouttest").await;
|
||||
let res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
client
|
||||
.post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
|
||||
.bearer_auth(&jwt)
|
||||
.json(&serde_json::json!({}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let deact_res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
|
||||
.query(&[("did", did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = deact_res.json().await.unwrap();
|
||||
assert_eq!(body["error"], "RepoDeactivated");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_checkout_takendown_account_returns_error() {
|
||||
let client = client();
|
||||
let base = base_url().await;
|
||||
let (admin_jwt, _) = create_admin_account_and_login(&client).await;
|
||||
let (_, target_did) = create_account_and_login(&client).await;
|
||||
let res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
|
||||
.query(&[("did", target_did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
client
|
||||
.post(format!("{}/xrpc/com.atproto.admin.updateSubjectStatus", base))
|
||||
.bearer_auth(&admin_jwt)
|
||||
.json(&serde_json::json!({
|
||||
"subject": {
|
||||
"$type": "com.atproto.admin.defs#repoRef",
|
||||
"did": target_did
|
||||
},
|
||||
"takedown": {
|
||||
"applied": true,
|
||||
"ref": "test-takedown"
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let takedown_res = client
|
||||
.get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
|
||||
.query(&[("did", target_did.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
|
||||
let body: Value = takedown_res.json().await.unwrap();
|
||||
assert_eq!(body["error"], "RepoTakendown");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user