mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-25 02:36:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546d342136 | ||
|
|
c680f3c419 | ||
|
|
806cb4b8c5 |
Generated
+16
-16
@@ -6094,7 +6094,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-auth"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base32",
|
||||
@@ -6117,7 +6117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-cache"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
@@ -6131,7 +6131,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-comms"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
@@ -6146,7 +6146,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-config"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"confique",
|
||||
"serde",
|
||||
@@ -6154,7 +6154,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-crypto"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64 0.22.1",
|
||||
@@ -6170,7 +6170,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-db"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -6187,7 +6187,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-db-traits"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
@@ -6203,7 +6203,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-infra"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -6214,7 +6214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-lexicon"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hickory-resolver",
|
||||
@@ -6232,7 +6232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-oauth"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -6255,7 +6255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-pds"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -6343,7 +6343,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-repo"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cid",
|
||||
@@ -6355,7 +6355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-ripple"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"backon",
|
||||
@@ -6380,7 +6380,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-scopes"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"futures",
|
||||
@@ -6396,7 +6396,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-storage"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
@@ -6413,7 +6413,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tranquil-types"
|
||||
version = "0.3.1"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cid",
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
|
||||
+16
-1
@@ -8,8 +8,23 @@ RUN apk add --no-cache ca-certificates musl-dev pkgconfig openssl-dev openssl-li
|
||||
WORKDIR /app
|
||||
ARG SLIM="false"
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY crates ./crates
|
||||
COPY .sqlx ./.sqlx
|
||||
COPY crates/tranquil-types ./crates/tranquil-types
|
||||
COPY crates/tranquil-crypto ./crates/tranquil-crypto
|
||||
COPY crates/tranquil-scopes ./crates/tranquil-scopes
|
||||
COPY crates/tranquil-config ./crates/tranquil-config
|
||||
COPY crates/tranquil-repo ./crates/tranquil-repo
|
||||
COPY crates/tranquil-lexicon ./crates/tranquil-lexicon
|
||||
COPY crates/tranquil-oauth ./crates/tranquil-oauth
|
||||
COPY crates/tranquil-db-traits ./crates/tranquil-db-traits
|
||||
COPY crates/tranquil-infra ./crates/tranquil-infra
|
||||
COPY crates/tranquil-auth ./crates/tranquil-auth
|
||||
COPY crates/tranquil-comms ./crates/tranquil-comms
|
||||
COPY crates/tranquil-db ./crates/tranquil-db
|
||||
COPY crates/tranquil-ripple ./crates/tranquil-ripple
|
||||
COPY crates/tranquil-storage ./crates/tranquil-storage
|
||||
COPY crates/tranquil-cache ./crates/tranquil-cache
|
||||
COPY crates/tranquil-pds ./crates/tranquil-pds
|
||||
COPY migrations ./crates/tranquil-pds/migrations
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/app/target \
|
||||
|
||||
@@ -327,12 +327,10 @@ impl TranquilConfig {
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
self.validate_sso_provider(prefix, p, errors);
|
||||
if p.get_enabled() {
|
||||
if p.get_issuer().is_none() {
|
||||
errors.push(format!(
|
||||
"{prefix}.issuer is required when {prefix}.enabled = true"
|
||||
));
|
||||
}
|
||||
if p.get_enabled() && p.get_issuer().is_none() {
|
||||
errors.push(format!(
|
||||
"{prefix}.issuer is required when {prefix}.enabled = true"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,7 @@ pub fn is_valid_uri(s: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn is_valid_cid(s: &str) -> bool {
|
||||
s.len() >= 8
|
||||
&& s.chars().all(|c| c.is_ascii_alphanumeric())
|
||||
&& s.starts_with(|c: char| c == 'b' || c == 'z' || c == 'Q')
|
||||
s.len() >= 8 && s.chars().all(|c| c.is_ascii_alphanumeric()) && s.starts_with(['b', 'z', 'Q'])
|
||||
}
|
||||
|
||||
pub fn is_valid_language(s: &str) -> bool {
|
||||
|
||||
@@ -339,15 +339,14 @@ fn validate_blob_ref(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max_size) = lex_blob.max_size {
|
||||
if let Some(size) = obj.get("size").and_then(|v| v.as_u64()) {
|
||||
if size > max_size {
|
||||
return Err(LexValidationError::field(
|
||||
path,
|
||||
format!("blob size {} exceeds max_size {}", size, max_size),
|
||||
));
|
||||
}
|
||||
}
|
||||
if let (Some(max_size), Some(size)) =
|
||||
(lex_blob.max_size, obj.get("size").and_then(|v| v.as_u64()))
|
||||
&& size > max_size
|
||||
{
|
||||
return Err(LexValidationError::field(
|
||||
path,
|
||||
format!("blob size {} exceeds max_size {}", size, max_size),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::AccountStatus;
|
||||
use crate::api::error::ApiError;
|
||||
use crate::auth::extractor::{Auth, Permissive};
|
||||
use crate::state::AppState;
|
||||
use crate::types::Did;
|
||||
use axum::http::Method;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
@@ -10,7 +9,6 @@ use axum::{
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::LazyLock;
|
||||
use tracing::{error, info, warn};
|
||||
@@ -59,109 +57,22 @@ pub struct GetServiceAuthOutput {
|
||||
|
||||
pub async fn get_service_auth(
|
||||
State(state): State<AppState>,
|
||||
headers: axum::http::HeaderMap,
|
||||
auth: Auth<Permissive>,
|
||||
Query(params): Query<GetServiceAuthParams>,
|
||||
) -> Response {
|
||||
let auth_header = crate::util::get_header_str(&headers, axum::http::header::AUTHORIZATION);
|
||||
let dpop_proof = crate::util::get_header_str(&headers, crate::util::HEADER_DPOP);
|
||||
info!(
|
||||
has_auth_header = auth_header.is_some(),
|
||||
has_dpop_proof = dpop_proof.is_some(),
|
||||
did = %&auth.did,
|
||||
is_oauth = auth.is_oauth(),
|
||||
aud = %params.aud,
|
||||
lxm = ?params.lxm,
|
||||
"getServiceAuth called"
|
||||
);
|
||||
let auth_header = match auth_header {
|
||||
Some(h) => h.trim(),
|
||||
None => {
|
||||
warn!("getServiceAuth: no Authorization header");
|
||||
return ApiError::AuthenticationRequired.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let extracted = match crate::auth::extract_auth_token_from_header(Some(auth_header)) {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
warn!(auth_scheme = ?auth_header.split_whitespace().next(), "getServiceAuth: invalid auth scheme");
|
||||
return ApiError::AuthenticationRequired.into_response();
|
||||
}
|
||||
};
|
||||
let token = extracted.token;
|
||||
|
||||
let auth_user = if extracted.scheme.is_dpop() {
|
||||
match crate::oauth::verify::verify_oauth_access_token(
|
||||
state.oauth_repo.as_ref(),
|
||||
&token,
|
||||
dpop_proof,
|
||||
Method::GET.as_str(),
|
||||
&crate::util::build_full_url(&format!(
|
||||
"/xrpc/com.atproto.server.getServiceAuth?aud={}&lxm={}",
|
||||
params.aud,
|
||||
params.lxm.as_ref().map_or("", |n| n.as_str())
|
||||
)),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
let did: Did = match result.did.parse() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return ApiError::InternalError(Some("Invalid DID in token".into()))
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
crate::auth::AuthenticatedUser {
|
||||
did,
|
||||
is_admin: false,
|
||||
status: AccountStatus::Active,
|
||||
scope: result.scope,
|
||||
key_bytes: None,
|
||||
controller_did: None,
|
||||
auth_source: crate::auth::AuthSource::OAuth,
|
||||
}
|
||||
}
|
||||
Err(crate::oauth::OAuthError::UseDpopNonce(nonce)) => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
[("DPoP-Nonce", nonce)],
|
||||
Json(json!({
|
||||
"error": "use_dpop_nonce",
|
||||
"message": "DPoP nonce required"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
Err(crate::oauth::OAuthError::ExpiredToken(msg)) => {
|
||||
warn!(error = %msg, "getServiceAuth DPoP token expired");
|
||||
return ApiError::OAuthExpiredToken(Some(msg)).into_response();
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(error = ?e, "getServiceAuth DPoP auth validation failed");
|
||||
return ApiError::AuthenticationFailed(Some(format!("{:?}", e))).into_response();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match crate::auth::validate_bearer_token_for_service_auth(state.user_repo.as_ref(), &token)
|
||||
.await
|
||||
{
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
warn!(error = ?e, "getServiceAuth auth validation failed");
|
||||
return ApiError::from(e).into_response();
|
||||
}
|
||||
}
|
||||
};
|
||||
info!(
|
||||
did = %&auth_user.did,
|
||||
is_oauth = auth_user.is_oauth(),
|
||||
has_key = auth_user.key_bytes.is_some(),
|
||||
"getServiceAuth auth validated"
|
||||
);
|
||||
let key_bytes = match &auth_user.key_bytes {
|
||||
let key_bytes = match &auth.key_bytes {
|
||||
Some(kb) => kb.clone(),
|
||||
None => {
|
||||
warn!(did = %&auth_user.did, "getServiceAuth: OAuth token has no key_bytes, fetching from DB");
|
||||
match state.user_repo.get_user_info_by_did(&auth_user.did).await {
|
||||
warn!(did = %&auth.did, "getServiceAuth: no key_bytes in auth, fetching from DB");
|
||||
match state.user_repo.get_user_info_by_did(&auth.did).await {
|
||||
Ok(Some(info)) => match info.key_bytes {
|
||||
Some(key_bytes_enc) => {
|
||||
match crate::config::decrypt_key(&key_bytes_enc, info.encryption_version) {
|
||||
@@ -202,15 +113,15 @@ pub async fn get_service_auth(
|
||||
|
||||
if let Some(method) = lxm {
|
||||
if let Err(e) = crate::auth::scope_check::check_rpc_scope(
|
||||
&auth_user.auth_source,
|
||||
auth_user.scope.as_deref(),
|
||||
&auth.auth_source,
|
||||
auth.scope.as_deref(),
|
||||
params.aud.as_str(),
|
||||
method.as_str(),
|
||||
) {
|
||||
return e;
|
||||
}
|
||||
} else if auth_user.is_oauth() {
|
||||
let permissions = auth_user.permissions();
|
||||
} else if auth.is_oauth() {
|
||||
let permissions = auth.permissions();
|
||||
if !permissions.has_full_access() {
|
||||
return ApiError::InvalidRequest(
|
||||
"OAuth tokens with granular scopes must specify an lxm parameter".into(),
|
||||
@@ -219,15 +130,7 @@ pub async fn get_service_auth(
|
||||
}
|
||||
}
|
||||
|
||||
let is_takendown = state
|
||||
.user_repo
|
||||
.get_status_by_did(&auth_user.did)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some_and(|s| s.takedown_ref.is_some());
|
||||
|
||||
if is_takendown && lxm != Some(&*CREATE_ACCOUNT_NSID) {
|
||||
if auth.status.is_takendown() && lxm != Some(&*CREATE_ACCOUNT_NSID) {
|
||||
return ApiError::InvalidToken(Some("Bad token scope".into())).into_response();
|
||||
}
|
||||
|
||||
@@ -265,7 +168,7 @@ pub async fn get_service_auth(
|
||||
}
|
||||
|
||||
let service_token = match crate::auth::create_service_token(
|
||||
&auth_user.did,
|
||||
&auth.did,
|
||||
params.aud.as_str(),
|
||||
lxm_for_token,
|
||||
&key_bytes,
|
||||
|
||||
@@ -68,7 +68,7 @@ pub async fn create_session(
|
||||
let pds_host = &tranquil_config::get().server.hostname;
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let normalized_identifier =
|
||||
NormalizedLoginIdentifier::normalize(&input.identifier, &hostname_for_handles);
|
||||
NormalizedLoginIdentifier::normalize(&input.identifier, hostname_for_handles);
|
||||
info!(
|
||||
"Normalized identifier: {} -> {}",
|
||||
input.identifier, normalized_identifier
|
||||
|
||||
@@ -355,7 +355,7 @@ async fn validate_bearer_token_with_options_internal(
|
||||
)
|
||||
.await;
|
||||
|
||||
let status_cache_key = crate::cache_keys::user_status_key(&did.to_string());
|
||||
let status_cache_key = crate::cache_keys::user_status_key(did.as_ref());
|
||||
let cached = CachedUserStatus {
|
||||
deactivated: user.deactivated_at.is_some(),
|
||||
takendown: user.takedown_ref.is_some(),
|
||||
@@ -394,7 +394,7 @@ async fn validate_bearer_token_with_options_internal(
|
||||
match verify_access_token_typed(token, &decrypted_key) {
|
||||
Ok(token_data) => {
|
||||
let jti = &token_data.claims.jti;
|
||||
let session_cache_key = crate::cache_keys::session_key(&did, &jti);
|
||||
let session_cache_key = crate::cache_keys::session_key(&did, jti);
|
||||
let mut session_valid = false;
|
||||
|
||||
if let Some(c) = cache {
|
||||
@@ -530,6 +530,7 @@ pub enum AccountRequirement {
|
||||
AnyStatus,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn validate_token_with_dpop(
|
||||
user_repo: &dyn UserRepository,
|
||||
oauth_repo: &dyn OAuthRepository,
|
||||
|
||||
@@ -653,8 +653,6 @@ pub fn app(state: AppState) -> Router {
|
||||
get(oauth::endpoints::oauth_authorization_server),
|
||||
);
|
||||
|
||||
if cfg!(feature = "frontend") {}
|
||||
|
||||
let router = Router::new()
|
||||
.nest_service("/xrpc", xrpc_service)
|
||||
.nest("/oauth", oauth_router)
|
||||
@@ -662,7 +660,7 @@ pub fn app(state: AppState) -> Router {
|
||||
.route("/metrics", get(metrics::metrics_handler))
|
||||
.route("/health", get(api::server::health))
|
||||
.route("/robots.txt", get(api::server::robots_txt))
|
||||
.route("/logo", get(api::server::get_logo))
|
||||
.route("/favicon.ico", get(api::server::get_logo))
|
||||
.route("/u/{handle}/did.json", get(api::identity::user_did_doc))
|
||||
.route(
|
||||
"/webhook/telegram",
|
||||
@@ -716,7 +714,7 @@ pub fn app(state: AppState) -> Router {
|
||||
|
||||
let spa_router = Router::new().fallback_service(ServeFile::new(&index_path));
|
||||
|
||||
let serve_dir = ServeDir::new(&frontend_dir).not_found_service(ServeFile::new(&index_path));
|
||||
let serve_dir = ServeDir::new(frontend_dir).not_found_service(ServeFile::new(&index_path));
|
||||
|
||||
return router
|
||||
.route(
|
||||
|
||||
@@ -256,7 +256,7 @@ pub async fn authorize_get(
|
||||
if let Some(ref login_hint) = request_data.parameters.login_hint {
|
||||
tracing::info!(login_hint = %login_hint, "Checking login_hint for delegation");
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let normalized = NormalizedLoginIdentifier::normalize(login_hint, &hostname_for_handles);
|
||||
let normalized = NormalizedLoginIdentifier::normalize(login_hint, hostname_for_handles);
|
||||
tracing::info!(normalized = %normalized, "Normalized login_hint");
|
||||
|
||||
match state
|
||||
@@ -530,7 +530,7 @@ pub async fn authorize_post(
|
||||
};
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let normalized_username =
|
||||
NormalizedLoginIdentifier::normalize(&form.username, &hostname_for_handles);
|
||||
NormalizedLoginIdentifier::normalize(&form.username, hostname_for_handles);
|
||||
tracing::debug!(
|
||||
original_username = %form.username,
|
||||
normalized_username = %normalized_username,
|
||||
@@ -2102,7 +2102,7 @@ pub async fn check_user_has_passkeys(
|
||||
) -> Response {
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let bare_identifier =
|
||||
BareLoginIdentifier::from_identifier(&query.identifier, &hostname_for_handles);
|
||||
BareLoginIdentifier::from_identifier(&query.identifier, hostname_for_handles);
|
||||
|
||||
let user = state
|
||||
.user_repo
|
||||
@@ -2134,7 +2134,7 @@ pub async fn check_user_security_status(
|
||||
) -> Response {
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let normalized_identifier =
|
||||
NormalizedLoginIdentifier::normalize(&query.identifier, &hostname_for_handles);
|
||||
NormalizedLoginIdentifier::normalize(&query.identifier, hostname_for_handles);
|
||||
|
||||
let user = state
|
||||
.user_repo
|
||||
@@ -2242,7 +2242,7 @@ pub async fn passkey_start(
|
||||
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let normalized_username =
|
||||
NormalizedLoginIdentifier::normalize(&form.identifier, &hostname_for_handles);
|
||||
NormalizedLoginIdentifier::normalize(&form.identifier, hostname_for_handles);
|
||||
|
||||
let user = match state
|
||||
.user_repo
|
||||
|
||||
@@ -774,10 +774,10 @@ pub async fn check_handle_available(
|
||||
};
|
||||
|
||||
let available_domains = tranquil_config::get().server.available_user_domain_list();
|
||||
if let Some(ref d) = query.domain {
|
||||
if !available_domains.iter().any(|ad| ad == d) {
|
||||
return Err(ApiError::InvalidRequest("Unknown user domain".into()));
|
||||
}
|
||||
if let Some(ref d) = query.domain
|
||||
&& !available_domains.iter().any(|ad| ad == d)
|
||||
{
|
||||
return Err(ApiError::InvalidRequest("Unknown user domain".into()));
|
||||
}
|
||||
let domain = query.domain.as_deref().unwrap_or(&available_domains[0]);
|
||||
let full_handle = format!("{}.{}", validated, domain);
|
||||
|
||||
@@ -224,7 +224,7 @@ impl AppState {
|
||||
.acquire_timeout(std::time::Duration::from_secs(acquire_timeout_secs))
|
||||
.idle_timeout(std::time::Duration::from_secs(300))
|
||||
.max_lifetime(std::time::Duration::from_secs(1800))
|
||||
.connect(&database_url)
|
||||
.connect(database_url)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to connect to Postgres: {}", e))?;
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ fn check_banned_content(
|
||||
check_string_field(obj, "description")?;
|
||||
}
|
||||
"app.bsky.feed.generator" => {
|
||||
if let Some(rkey) = rkey {
|
||||
if crate::moderation::has_explicit_slur(rkey) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: "rkey".to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(rkey) = rkey
|
||||
&& crate::moderation::has_explicit_slur(rkey)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: "rkey".to_string(),
|
||||
});
|
||||
}
|
||||
check_string_field(obj, "displayName")?;
|
||||
}
|
||||
@@ -169,12 +169,12 @@ fn check_banned_content(
|
||||
fn check_post_banned_content(obj: &serde_json::Map<String, Value>) -> Result<(), ValidationError> {
|
||||
if let Some(tags) = obj.get("tags").and_then(|v| v.as_array()) {
|
||||
tags.iter().enumerate().try_for_each(|(i, tag)| {
|
||||
if let Some(tag_str) = tag.as_str() {
|
||||
if crate::moderation::has_explicit_slur(tag_str) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("tags/{}", i),
|
||||
});
|
||||
}
|
||||
if let Some(tag_str) = tag.as_str()
|
||||
&& crate::moderation::has_explicit_slur(tag_str)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("tags/{}", i),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -187,14 +187,13 @@ fn check_post_banned_content(obj: &serde_json::Map<String, Value>) -> Result<(),
|
||||
.get("$type")
|
||||
.and_then(|v| v.as_str())
|
||||
.is_some_and(|t| t == "app.bsky.richtext.facet#tag");
|
||||
if is_tag {
|
||||
if let Some(tag) = feature.get("tag").and_then(|v| v.as_str()) {
|
||||
if crate::moderation::has_explicit_slur(tag) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("facets/{}/features/{}/tag", i, j),
|
||||
});
|
||||
}
|
||||
}
|
||||
if is_tag
|
||||
&& let Some(tag) = feature.get("tag").and_then(|v| v.as_str())
|
||||
&& crate::moderation::has_explicit_slur(tag)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("facets/{}/features/{}/tag", i, j),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -209,12 +208,12 @@ fn check_string_field(
|
||||
obj: &serde_json::Map<String, Value>,
|
||||
field: &str,
|
||||
) -> Result<(), ValidationError> {
|
||||
if let Some(value) = obj.get(field).and_then(|v| v.as_str()) {
|
||||
if crate::moderation::has_explicit_slur(value) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: field.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(value) = obj.get(field).and_then(|v| v.as_str())
|
||||
&& crate::moderation::has_explicit_slur(value)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: field.to_string(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" type="image/png">
|
||||
<title>Tranquil PDS</title>
|
||||
<style>
|
||||
:root {
|
||||
@@ -367,7 +368,7 @@
|
||||
|
||||
<nav>
|
||||
<div class="nav-left">
|
||||
<img src="/logo" alt="Logo" class="nav-logo hidden" id="navLogo">
|
||||
<img src="/favicon.ico" alt="Logo" class="nav-logo hidden" id="navLogo">
|
||||
<span class="hostname" id="hostname">loading...</span>
|
||||
<span class="user-count hidden" id="userCount"></span>
|
||||
</div>
|
||||
@@ -556,7 +557,7 @@
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
fetch("/logo", { method: "HEAD" })
|
||||
fetch("/favicon.ico", { method: "HEAD" })
|
||||
.then((r) => {
|
||||
if (r.ok) {
|
||||
document.getElementById("navLogo").classList.remove(
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
logoCid = config.logoCid
|
||||
originalLogoCid = config.logoCid
|
||||
if (config.logoCid) {
|
||||
logoPreview = '/logo'
|
||||
logoPreview = '/favicon.ico'
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(e instanceof ApiError ? e.message : $_('admin.failedToLoadConfig'))
|
||||
|
||||
@@ -64,7 +64,7 @@ function setFavicon(hasLogo: boolean) {
|
||||
link.rel = "icon";
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = "/logo";
|
||||
link.href = "/favicon.ico";
|
||||
} else if (link) {
|
||||
link.remove();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user