Better oauth, appview groundwork

This commit is contained in:
lewis
2025-12-17 23:29:48 +02:00
parent dea6c09aa0
commit 2cf87e2cfb
45 changed files with 4586 additions and 906 deletions
+56 -6
View File
@@ -1,21 +1,75 @@
use crate::api::proxy_client::proxy_client;
use crate::state::AppState;
use axum::{
Json,
extract::{Query, State},
extract::{Query, RawQuery, State},
http::StatusCode,
response::{IntoResponse, Response},
};
use serde::Deserialize;
use serde_json::json;
use tracing::{error, info};
#[derive(Deserialize)]
pub struct DescribeRepoInput {
pub repo: String,
}
async fn proxy_describe_repo_to_appview(state: &AppState, raw_query: Option<&str>) -> Response {
let resolved = match state.appview_registry.get_appview_for_method("com.atproto.repo.describeRepo").await {
Some(r) => r,
None => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
}
};
let target_url = match raw_query {
Some(q) => format!("{}/xrpc/com.atproto.repo.describeRepo?{}", resolved.url, q),
None => format!("{}/xrpc/com.atproto.repo.describeRepo", resolved.url),
};
info!("Proxying describeRepo to AppView: {}", target_url);
let client = proxy_client();
match client.get(&target_url).send().await {
Ok(resp) => {
let status =
StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY);
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
match resp.bytes().await {
Ok(body) => {
let mut builder = Response::builder().status(status);
if let Some(ct) = content_type {
builder = builder.header("content-type", ct);
}
builder
.body(axum::body::Body::from(body))
.unwrap_or_else(|_| {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response()
})
}
Err(e) => {
error!("Error reading AppView response: {:?}", e);
(StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response()
}
}
}
Err(e) => {
error!("Error proxying to AppView: {:?}", e);
(StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response()
}
}
}
pub async fn describe_repo(
State(state): State<AppState>,
Query(input): Query<DescribeRepoInput>,
RawQuery(raw_query): RawQuery,
) -> Response {
let user_row = if input.repo.starts_with("did:") {
sqlx::query!(
@@ -37,11 +91,7 @@ pub async fn describe_repo(
let (user_id, handle, did) = match user_row {
Ok(Some((id, handle, did))) => (id, handle, did),
_ => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
return proxy_describe_repo_to_appview(&state, raw_query.as_deref()).await;
}
};
let collections_query = sqlx::query!(
+118 -12
View File
@@ -1,7 +1,8 @@
use crate::api::proxy_client::proxy_client;
use crate::state::AppState;
use axum::{
Json,
extract::{Query, State},
extract::{Query, RawQuery, State},
http::StatusCode,
response::{IntoResponse, Response},
};
@@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use std::str::FromStr;
use tracing::error;
use tracing::{error, info};
#[derive(Deserialize)]
pub struct GetRecordInput {
@@ -21,9 +22,69 @@ pub struct GetRecordInput {
pub cid: Option<String>,
}
async fn proxy_get_record_to_appview(state: &AppState, raw_query: Option<&str>) -> Response {
let resolved = match state.appview_registry.get_appview_for_method("com.atproto.repo.getRecord").await {
Some(r) => r,
None => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
}
};
let target_url = match raw_query {
Some(q) => format!("{}/xrpc/com.atproto.repo.getRecord?{}", resolved.url, q),
None => format!("{}/xrpc/com.atproto.repo.getRecord", resolved.url),
};
info!("Proxying getRecord to AppView: {}", target_url);
let client = proxy_client();
match client.get(&target_url).send().await {
Ok(resp) => {
let status =
StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY);
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
match resp.bytes().await {
Ok(body) => {
let mut builder = Response::builder().status(status);
if let Some(ct) = content_type {
builder = builder.header("content-type", ct);
}
builder
.body(axum::body::Body::from(body))
.unwrap_or_else(|_| {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response()
})
}
Err(e) => {
error!("Error reading AppView response: {:?}", e);
(
StatusCode::BAD_GATEWAY,
Json(json!({"error": "UpstreamError"})),
)
.into_response()
}
}
}
Err(e) => {
error!("Error proxying to AppView: {:?}", e);
(
StatusCode::BAD_GATEWAY,
Json(json!({"error": "UpstreamError"})),
)
.into_response()
}
}
}
pub async fn get_record(
State(state): State<AppState>,
Query(input): Query<GetRecordInput>,
RawQuery(raw_query): RawQuery,
) -> Response {
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
let user_id_opt = if input.repo.starts_with("did:") {
@@ -46,11 +107,7 @@ pub async fn get_record(
let user_id: uuid::Uuid = match user_id_opt {
Ok(Some(id)) => id,
_ => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
return proxy_get_record_to_appview(&state, raw_query.as_deref()).await;
}
};
let record_row = sqlx::query!(
@@ -134,9 +191,62 @@ pub struct ListRecordsOutput {
pub cursor: Option<String>,
pub records: Vec<serde_json::Value>,
}
async fn proxy_list_records_to_appview(state: &AppState, raw_query: Option<&str>) -> Response {
let resolved = match state.appview_registry.get_appview_for_method("com.atproto.repo.listRecords").await {
Some(r) => r,
None => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
}
};
let target_url = match raw_query {
Some(q) => format!("{}/xrpc/com.atproto.repo.listRecords?{}", resolved.url, q),
None => format!("{}/xrpc/com.atproto.repo.listRecords", resolved.url),
};
info!("Proxying listRecords to AppView: {}", target_url);
let client = proxy_client();
match client.get(&target_url).send().await {
Ok(resp) => {
let status =
StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY);
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
match resp.bytes().await {
Ok(body) => {
let mut builder = Response::builder().status(status);
if let Some(ct) = content_type {
builder = builder.header("content-type", ct);
}
builder
.body(axum::body::Body::from(body))
.unwrap_or_else(|_| {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response()
})
}
Err(e) => {
error!("Error reading AppView response: {:?}", e);
(StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response()
}
}
}
Err(e) => {
error!("Error proxying to AppView: {:?}", e);
(StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response()
}
}
}
pub async fn list_records(
State(state): State<AppState>,
Query(input): Query<ListRecordsInput>,
RawQuery(raw_query): RawQuery,
) -> Response {
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
let user_id_opt = if input.repo.starts_with("did:") {
@@ -159,11 +269,7 @@ pub async fn list_records(
let user_id: uuid::Uuid = match user_id_opt {
Ok(Some(id)) => id,
_ => {
return (
StatusCode::NOT_FOUND,
Json(json!({"error": "NotFound", "message": "Repo not found"})),
)
.into_response();
return proxy_list_records_to_appview(&state, raw_query.as_deref()).await;
}
};
let limit = input.limit.unwrap_or(50).clamp(1, 100);