fix: move code to more correct crates

This commit is contained in:
Lewis
2026-03-17 10:16:36 +00:00
committed by Tangled
parent 92e609d367
commit d5ed420dd7
114 changed files with 1730 additions and 1404 deletions
Generated
+142 -19
View File
@@ -6092,9 +6092,57 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "tranquil-api"
version = "0.4.2"
dependencies = [
"anyhow",
"axum",
"backon",
"base32",
"base64 0.22.1",
"bcrypt",
"bs58",
"bytes",
"chrono",
"cid",
"ed25519-dalek",
"futures",
"hex",
"http 1.4.0",
"infer",
"ipld-core",
"jacquard-common",
"jacquard-repo",
"k256",
"multibase",
"multihash",
"rand 0.8.5",
"reqwest",
"serde",
"serde_ipld_dagcbor",
"serde_json",
"sha2",
"subtle",
"thiserror 2.0.17",
"tokio",
"tracing",
"tranquil-config",
"tranquil-db",
"tranquil-db-traits",
"tranquil-lexicon",
"tranquil-pds",
"tranquil-scopes",
"tranquil-types",
"urlencoding",
"uuid",
"webauthn-rs",
"zip",
]
[[package]]
name = "tranquil-auth"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"anyhow",
"base32",
@@ -6117,7 +6165,7 @@ dependencies = [
[[package]]
name = "tranquil-cache"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -6131,7 +6179,7 @@ dependencies = [
[[package]]
name = "tranquil-comms"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -6146,7 +6194,7 @@ dependencies = [
[[package]]
name = "tranquil-config"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"confique",
"serde",
@@ -6154,7 +6202,7 @@ dependencies = [
[[package]]
name = "tranquil-crypto"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"aes-gcm",
"base64 0.22.1",
@@ -6170,7 +6218,7 @@ dependencies = [
[[package]]
name = "tranquil-db"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"chrono",
@@ -6187,7 +6235,7 @@ dependencies = [
[[package]]
name = "tranquil-db-traits"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -6203,7 +6251,7 @@ dependencies = [
[[package]]
name = "tranquil-infra"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"bytes",
@@ -6214,7 +6262,7 @@ dependencies = [
[[package]]
name = "tranquil-lexicon"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"chrono",
"hickory-resolver",
@@ -6232,7 +6280,7 @@ dependencies = [
[[package]]
name = "tranquil-oauth"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"anyhow",
"axum",
@@ -6253,9 +6301,42 @@ dependencies = [
"uuid",
]
[[package]]
name = "tranquil-oauth-server"
version = "0.4.2"
dependencies = [
"axum",
"base64 0.22.1",
"bcrypt",
"chrono",
"cid",
"hmac",
"http 1.4.0",
"jacquard-common",
"jacquard-repo",
"k256",
"rand 0.8.5",
"serde",
"serde_json",
"serde_urlencoded",
"sha2",
"subtle",
"tokio",
"tracing",
"tranquil-api",
"tranquil-config",
"tranquil-crypto",
"tranquil-db-traits",
"tranquil-pds",
"tranquil-types",
"urlencoding",
"uuid",
"webauthn-rs",
]
[[package]]
name = "tranquil-pds"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"aes-gcm",
"anyhow",
@@ -6272,9 +6353,7 @@ dependencies = [
"chrono",
"ciborium",
"cid",
"clap",
"ctor",
"dotenvy",
"ed25519-dalek",
"futures",
"futures-util",
@@ -6319,7 +6398,7 @@ dependencies = [
"tower-http",
"tower-layer",
"tracing",
"tracing-subscriber",
"tranquil-api",
"tranquil-auth",
"tranquil-cache",
"tranquil-comms",
@@ -6329,10 +6408,12 @@ dependencies = [
"tranquil-db-traits",
"tranquil-lexicon",
"tranquil-oauth",
"tranquil-oauth-server",
"tranquil-repo",
"tranquil-ripple",
"tranquil-scopes",
"tranquil-storage",
"tranquil-sync",
"tranquil-types",
"urlencoding",
"uuid",
@@ -6343,7 +6424,7 @@ dependencies = [
[[package]]
name = "tranquil-repo"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"bytes",
"cid",
@@ -6355,7 +6436,7 @@ dependencies = [
[[package]]
name = "tranquil-ripple"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"backon",
@@ -6380,7 +6461,7 @@ dependencies = [
[[package]]
name = "tranquil-scopes"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"axum",
"futures",
@@ -6394,9 +6475,29 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "tranquil-server"
version = "0.4.2"
dependencies = [
"axum",
"clap",
"dotenvy",
"ed25519-dalek",
"hex",
"tokio",
"tokio-util",
"tracing",
"tracing-subscriber",
"tranquil-api",
"tranquil-config",
"tranquil-oauth-server",
"tranquil-pds",
"tranquil-sync",
]
[[package]]
name = "tranquil-storage"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"async-trait",
"aws-config",
@@ -6411,9 +6512,31 @@ dependencies = [
"uuid",
]
[[package]]
name = "tranquil-sync"
version = "0.4.2"
dependencies = [
"anyhow",
"axum",
"bytes",
"chrono",
"cid",
"futures",
"ipld-core",
"jacquard-repo",
"serde",
"serde_ipld_dagcbor",
"tokio",
"tracing",
"tranquil-config",
"tranquil-db-traits",
"tranquil-pds",
"tranquil-types",
]
[[package]]
name = "tranquil-types"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"chrono",
"cid",
+10 -1
View File
@@ -16,11 +16,15 @@ members = [
"crates/tranquil-db-traits",
"crates/tranquil-db",
"crates/tranquil-pds",
"crates/tranquil-server",
"crates/tranquil-sync",
"crates/tranquil-oauth-server",
"crates/tranquil-api",
"crates/tranquil-lexicon",
]
[workspace.package]
version = "0.4.1"
version = "0.4.2"
edition = "2024"
license = "AGPL-3.0-or-later"
@@ -40,6 +44,11 @@ tranquil-db-traits = { path = "crates/tranquil-db-traits" }
tranquil-db = { path = "crates/tranquil-db" }
tranquil-ripple = { path = "crates/tranquil-ripple" }
tranquil-lexicon = { path = "crates/tranquil-lexicon" }
tranquil-pds = { path = "crates/tranquil-pds" }
tranquil-server = { path = "crates/tranquil-server" }
tranquil-sync = { path = "crates/tranquil-sync" }
tranquil-oauth-server = { path = "crates/tranquil-oauth-server" }
tranquil-api = { path = "crates/tranquil-api" }
unicode-segmentation = "1"
+7 -3
View File
@@ -25,15 +25,19 @@ 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 crates/tranquil-sync ./crates/tranquil-sync
COPY crates/tranquil-api ./crates/tranquil-api
COPY crates/tranquil-oauth-server ./crates/tranquil-oauth-server
COPY crates/tranquil-server ./crates/tranquil-server
COPY migrations ./crates/tranquil-pds/migrations
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
if [ "$SLIM" = "true" ]; then \
SQLX_OFFLINE=true cargo build --release -p tranquil-pds --no-default-features; \
SQLX_OFFLINE=true cargo build --release -p tranquil-server --no-default-features; \
else \
SQLX_OFFLINE=true cargo build --release -p tranquil-pds; \
SQLX_OFFLINE=true cargo build --release -p tranquil-server; \
fi && \
cp target/release/tranquil-pds /tmp/tranquil-pds
cp target/release/tranquil-server /tmp/tranquil-pds
FROM alpine:3.23 AS signal-cli
RUN apk add --no-cache curl tar
+50
View File
@@ -0,0 +1,50 @@
[package]
name = "tranquil-api"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
tranquil-pds = { workspace = true }
tranquil-types = { workspace = true }
tranquil-config = { workspace = true }
tranquil-db = { workspace = true }
tranquil-db-traits = { workspace = true }
tranquil-lexicon = { workspace = true, features = ["resolve"] }
tranquil-scopes = { workspace = true }
anyhow = { workspace = true }
axum = { workspace = true }
backon = { workspace = true }
base32 = { workspace = true }
base64 = { workspace = true }
bcrypt = { workspace = true }
bs58 = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
cid = { workspace = true }
ed25519-dalek = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
http = { workspace = true }
infer = { workspace = true }
ipld-core = { workspace = true }
jacquard-common = { workspace = true }
jacquard-repo = { workspace = true }
k256 = { workspace = true }
multibase = { workspace = true }
multihash = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_ipld_dagcbor = { workspace = true }
sha2 = { workspace = true }
subtle = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
urlencoding = { workspace = true }
uuid = { workspace = true }
webauthn-rs = { workspace = true }
zip = { workspace = true }
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::auth::{Auth, NotTakendown, Permissive};
use crate::state::AppState;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Auth, NotTakendown, Permissive};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -1,8 +1,8 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::Did;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::Did;
use axum::{
Json,
extract::State,
@@ -36,7 +36,7 @@ pub async fn delete_account(
.await
.log_db_err("deleting account")?;
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
did,
tranquil_db_traits::AccountStatus::Deleted,
@@ -50,7 +50,7 @@ pub async fn delete_account(
}
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&handle))
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
.await;
Ok(EmptyResponse::ok().into_response())
}
@@ -1,7 +1,7 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::Did;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::Did;
use axum::{
Json,
extract::State,
@@ -1,7 +1,7 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::{Did, Handle};
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle};
use axum::{
Json,
extract::{Query, RawQuery, State},
@@ -196,7 +196,7 @@ pub async fn get_account_infos(
_auth: Auth<Admin>,
RawQuery(raw_query): RawQuery,
) -> Result<Response, ApiError> {
let dids: Vec<String> = crate::util::parse_repeated_query_param(raw_query.as_deref(), "dids")
let dids: Vec<String> = tranquil_pds::util::parse_repeated_query_param(raw_query.as_deref(), "dids")
.into_iter()
.filter(|d| !d.is_empty())
.collect();
@@ -1,7 +1,7 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::{Did, Handle};
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle};
use axum::{
Json,
extract::{Query, State},
@@ -1,8 +1,8 @@
use crate::api::EmptyResponse;
use crate::api::error::ApiError;
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::{Did, Handle, PlainPassword};
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle, PlainPassword};
use axum::{
Json,
extract::State,
@@ -101,14 +101,14 @@ pub async fn update_account_handle(
if let Some(old) = old_handle {
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&old))
.delete(&tranquil_pds::cache_keys::handle_key(&old))
.await;
}
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&handle))
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
.await;
if let Err(e) = crate::api::repo::record::sequence_identity_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(
&state,
did,
Some(&handle_for_check),
@@ -121,7 +121,7 @@ pub async fn update_account_handle(
);
}
if let Err(e) =
crate::api::identity::did::update_plc_handle(&state, did, &handle_for_check).await
crate::identity::did::update_plc_handle(&state, did, &handle_for_check).await
{
warn!("Failed to update PLC handle for admin handle update: {}", e);
}
@@ -1,6 +1,6 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use axum::{Json, extract::State};
use serde::{Deserialize, Serialize};
use tracing::{error, warn};
@@ -1,7 +1,7 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::{Query, State},
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -1,7 +1,7 @@
use crate::api::error::ApiError;
use crate::auth::{Admin, Auth};
use crate::state::AppState;
use crate::types::{CidLink, Did};
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Admin, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{CidLink, Did};
use axum::{
Json,
extract::{Query, State},
@@ -215,7 +215,7 @@ pub async fn update_subject_status(
tranquil_db_traits::AccountStatus::Active
};
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, status).await
tranquil_pds::repo_ops::sequence_account_event(&state, &did, status).await
{
warn!("Failed to sequence account event for takedown: {}", e);
}
@@ -227,7 +227,7 @@ pub async fn update_subject_status(
tranquil_db_traits::AccountStatus::Active
};
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, status).await
tranquil_pds::repo_ops::sequence_account_event(&state, &did, status).await
{
warn!("Failed to sequence account event for deactivation: {}", e);
}
@@ -235,7 +235,7 @@ pub async fn update_subject_status(
if let Ok(Some(handle)) = state.user_repo.get_handle_by_did(&did).await {
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&handle))
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
.await;
}
return Ok((
@@ -1,5 +1,5 @@
use crate::auth::{AccountRequirement, extract_auth_token_from_header, validate_token_with_dpop};
use crate::state::AppState;
use tranquil_pds::auth::{AccountRequirement, extract_auth_token_from_header, validate_token_with_dpop};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -33,13 +33,13 @@ pub async fn get_age_assurance_state() -> Response {
}
async fn get_account_created_at(state: &AppState, headers: &HeaderMap) -> Option<String> {
let auth_header = crate::util::get_header_str(headers, http::header::AUTHORIZATION);
let auth_header = tranquil_pds::util::get_header_str(headers, http::header::AUTHORIZATION);
tracing::debug!(?auth_header, "age assurance: extracting token");
let extracted = extract_auth_token_from_header(auth_header)?;
tracing::debug!("age assurance: got token, validating");
let dpop_proof = crate::util::get_header_str(headers, crate::util::HEADER_DPOP);
let dpop_proof = tranquil_pds::util::get_header_str(headers, tranquil_pds::util::HEADER_DPOP);
let http_uri = "/";
let auth_user = match validate_token_with_dpop(
@@ -1,9 +1,9 @@
use crate::api::error::ApiError;
use crate::api::{EmptyResponse, EnabledResponse};
use crate::auth::{Active, Auth};
use crate::scheduled::generate_full_backup;
use crate::state::AppState;
use crate::storage::{BackupStorage, backup_retention_count};
use tranquil_pds::api::error::ApiError;
use tranquil_pds::api::{EmptyResponse, EnabledResponse};
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::scheduled::generate_full_backup;
use tranquil_pds::state::AppState;
use tranquil_pds::storage::{BackupStorage, backup_retention_count};
use anyhow::Context;
use axum::{
Json,
@@ -39,7 +39,7 @@ pub struct ListBackupsOutput {
pub async fn list_backups(
State(state): State<AppState>,
auth: Auth<Active>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let (user_id, backup_enabled) = match state.backup_repo.get_user_backup_status(&auth.did).await
{
Ok(Some(status)) => status,
@@ -91,7 +91,7 @@ pub async fn get_backup(
State(state): State<AppState>,
auth: Auth<Active>,
Query(query): Query<GetBackupQuery>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let backup_id = match uuid::Uuid::parse_str(&query.id) {
Ok(id) => id,
Err(_) => {
@@ -157,7 +157,7 @@ pub struct CreateBackupOutput {
pub async fn create_backup(
State(state): State<AppState>,
auth: Auth<Active>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let backup_storage = match state.backup_storage.as_ref() {
Some(storage) => storage,
None => {
@@ -213,7 +213,7 @@ pub async fn create_backup(
}
};
let block_count = crate::scheduled::count_car_blocks(&car_bytes);
let block_count = tranquil_pds::scheduled::count_car_blocks(&car_bytes);
let size_bytes = i64::try_from(car_bytes.len()).unwrap_or(i64::MAX);
let storage_key = match backup_storage
@@ -327,7 +327,7 @@ pub async fn delete_backup(
State(state): State<AppState>,
auth: Auth<Active>,
Query(query): Query<DeleteBackupQuery>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let backup_id = match uuid::Uuid::parse_str(&query.id) {
Ok(id) => id,
Err(_) => {
@@ -384,7 +384,7 @@ pub async fn set_backup_enabled(
State(state): State<AppState>,
auth: Auth<Active>,
Json(input): Json<SetBackupEnabledInput>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let deactivated_at = match state
.backup_repo
.get_user_deactivated_status(&auth.did)
@@ -423,7 +423,7 @@ pub async fn set_backup_enabled(
pub async fn export_blobs(
State(state): State<AppState>,
auth: Auth<Active>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let user_id = match state.backup_repo.get_user_id_by_did(&auth.did).await {
Ok(Some(id)) => id,
Ok(None) => {
@@ -1,13 +1,13 @@
use crate::api::error::ApiError;
use crate::api::repo::record::utils::create_signed_commit;
use crate::auth::{Active, Auth};
use crate::delegation::{
use tranquil_pds::api::error::ApiError;
use tranquil_pds::repo_ops::create_signed_commit;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::delegation::{
DelegationActionType, SCOPE_PRESETS, ValidatedDelegationScope, verify_can_add_controllers,
verify_can_be_controller, verify_can_control_accounts,
};
use crate::rate_limit::{AccountCreationLimit, RateLimited};
use crate::state::AppState;
use crate::types::{Did, Handle};
use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle};
use axum::{
Json,
extract::{Query, State},
@@ -449,7 +449,7 @@ pub async fn create_delegated_account(
.unwrap_or(&input.handle),
None => &input.handle,
};
match crate::api::validation::validate_short_handle(handle_to_validate) {
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
Ok(h) => format!("{}.{}", h, matched_domain.unwrap_or(&available_domains[0])),
Err(e) => {
return Ok(ApiError::InvalidRequest(e.to_string()).into_response());
@@ -465,7 +465,7 @@ pub async fn create_delegated_account(
.map(|e| e.trim().to_string())
.filter(|e| !e.is_empty());
if let Some(ref email) = email
&& !crate::api::validation::is_valid_email(email)
&& !tranquil_pds::api::validation::is_valid_email(email)
{
return Ok(ApiError::InvalidEmail.into_response());
}
@@ -502,9 +502,9 @@ pub async fn create_delegated_account(
.secrets
.plc_rotation_key
.clone()
.unwrap_or_else(|| crate::plc::signing_key_to_did_key(&signing_key));
.unwrap_or_else(|| tranquil_pds::plc::signing_key_to_did_key(&signing_key));
let genesis_result = match crate::plc::create_genesis_operation(
let genesis_result = match tranquil_pds::plc::create_genesis_operation(
&signing_key,
&rotation_key,
&handle,
@@ -520,7 +520,7 @@ pub async fn create_delegated_account(
}
};
let plc_client = crate::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
if let Err(e) = plc_client
.send_operation(&genesis_result.did, &genesis_result.signed_operation)
.await
@@ -540,7 +540,7 @@ pub async fn create_delegated_account(
let handle: Handle = handle.parse().map_err(|_| ApiError::InvalidHandle(None))?;
info!(did = %did, handle = %handle, controller = %can_control.did(), "Created DID for delegated account");
let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) {
let encrypted_key_bytes = match tranquil_pds::config::encrypt_key(&secret_key_bytes) {
Ok(bytes) => bytes,
Err(e) => {
error!("Error encrypting signing key: {:?}", e);
@@ -581,7 +581,7 @@ pub async fn create_delegated_account(
controller_did: can_control.did().clone(),
controller_scopes: input.controller_scopes.as_str().to_string(),
encrypted_key_bytes,
encryption_version: crate::config::ENCRYPTION_VERSION,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
commit_cid: commit_cid.to_string(),
repo_rev: rev.as_ref().to_string(),
genesis_block_cids,
@@ -616,11 +616,11 @@ pub async fn create_delegated_account(
}
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&handle)).await
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await
{
warn!("Failed to sequence identity event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did,
tranquil_db_traits::AccountStatus::Active,
@@ -634,11 +634,11 @@ pub async fn create_delegated_account(
"$type": "app.bsky.actor.profile",
"displayName": handle
});
if let Err(e) = crate::api::repo::record::create_record_internal(
if let Err(e) = tranquil_pds::repo_ops::create_record_internal(
&state,
&did,
&crate::types::PROFILE_COLLECTION,
&crate::types::PROFILE_RKEY,
&tranquil_pds::types::PROFILE_COLLECTION,
&tranquil_pds::types::PROFILE_RKEY,
&profile_record,
)
.await
@@ -10,9 +10,9 @@ use serde_json::json;
use tracing::{debug, info, warn};
use tranquil_types::Handle;
use crate::comms::comms_repo;
use crate::state::AppState;
use crate::util::discord_public_key;
use tranquil_pds::comms::comms_repo;
use tranquil_pds::state::AppState;
use tranquil_pds::util::discord_public_key;
#[derive(Deserialize)]
struct Interaction {
@@ -1,12 +1,12 @@
use super::did::verify_did_web;
use crate::api::error::ApiError;
use crate::api::repo::record::utils::create_signed_commit;
use crate::auth::{ServiceTokenVerifier, extract_auth_token_from_header, is_service_token};
use crate::plc::{PlcClient, create_genesis_operation, signing_key_to_did_key};
use crate::rate_limit::{AccountCreationLimit, RateLimited};
use crate::state::AppState;
use crate::types::{Did, Handle, PlainPassword};
use crate::validation::validate_password;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::repo_ops::create_signed_commit;
use tranquil_pds::auth::{ServiceTokenVerifier, extract_auth_token_from_header, is_service_token};
use tranquil_pds::plc::{PlcClient, create_genesis_operation, signing_key_to_did_key};
use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle, PlainPassword};
use tranquil_pds::validation::validate_password;
use axum::{
Json,
extract::State,
@@ -73,7 +73,7 @@ pub async fn create_account(
}
let migration_auth = if let Some(extracted) = extract_auth_token_from_header(
crate::util::get_header_str(&headers, http::header::AUTHORIZATION),
tranquil_pds::util::get_header_str(&headers, http::header::AUTHORIZATION),
) {
let token = extracted.token;
if is_service_token(&token) {
@@ -155,14 +155,14 @@ pub async fn create_account(
.unwrap_or(&input.handle),
None => &input.handle,
};
match crate::api::validation::validate_short_handle(handle_to_validate) {
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
Ok(h) => h,
Err(e) => {
return ApiError::from(e).into_response();
}
}
} else {
match crate::api::validation::validate_full_domain_handle(&input.handle) {
match tranquil_pds::api::validation::validate_full_domain_handle(&input.handle) {
Ok(h) => h,
Err(e) => return ApiError::from(e).into_response(),
}
@@ -173,7 +173,7 @@ pub async fn create_account(
.map(|e| e.trim().to_string())
.filter(|e| !e.is_empty());
if let Some(ref email) = email
&& !crate::api::validation::is_valid_email(email)
&& !tranquil_pds::api::validation::is_valid_email(email)
{
return ApiError::InvalidEmail.into_response();
}
@@ -191,7 +191,7 @@ pub async fn create_account(
tranquil_db_traits::CommsChannel::Discord => match &input.discord_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().to_lowercase();
if !crate::api::validation::is_valid_discord_username(&clean) {
if !tranquil_pds::api::validation::is_valid_discord_username(&clean) {
return ApiError::InvalidRequest(
"Invalid Discord username. Must be 2-32 lowercase characters (letters, numbers, underscores, periods)".into(),
).into_response();
@@ -203,7 +203,7 @@ pub async fn create_account(
tranquil_db_traits::CommsChannel::Telegram => match &input.telegram_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().trim_start_matches('@');
if !crate::api::validation::is_valid_telegram_username(clean) {
if !tranquil_pds::api::validation::is_valid_telegram_username(clean) {
return ApiError::InvalidRequest(
"Invalid Telegram username. Must be 5-32 characters, alphanumeric or underscore".into(),
).into_response();
@@ -257,7 +257,7 @@ pub async fn create_account(
let did_type = input.did_type.as_deref().unwrap_or("plc");
let did = match did_type {
"web" => {
if !crate::api::server::meta::is_self_hosted_did_web_enabled() {
if !tranquil_pds::util::is_self_hosted_did_web_enabled() {
return ApiError::SelfHostedDidWebDisabled.into_response();
}
let encoded_handle = handle.replace(':', "%3A");
@@ -408,7 +408,7 @@ pub async fn create_account(
.await
{
Ok(Some(key_info)) => {
match crate::config::decrypt_key(
match tranquil_pds::config::decrypt_key(
&key_info.key_bytes,
key_info.encryption_version,
) {
@@ -428,14 +428,14 @@ pub async fn create_account(
}
};
let access_meta =
match crate::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
Ok(m) => m,
Err(e) => {
error!("Error creating access token: {:?}", e);
return ApiError::InternalError(None).into_response();
}
};
let refresh_meta = match crate::auth::create_refresh_token_with_metadata(
let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(
&did,
&secret_key_bytes,
) {
@@ -463,12 +463,12 @@ pub async fn create_account(
}
let hostname = &tranquil_config::get().server.hostname;
let verification_required = if let Some(ref user_email) = email {
let token = crate::auth::verification_token::generate_migration_token(
let token = tranquil_pds::auth::verification_token::generate_migration_token(
&did_typed, user_email,
);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&token);
if let Err(e) = crate::comms::comms_repo::enqueue_migration_verification(
tranquil_pds::auth::verification_token::format_token_for_display(&token);
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_migration_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
reactivated.user_id,
@@ -590,7 +590,7 @@ pub async fn create_account(
None
};
let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) {
let encrypted_key_bytes = match tranquil_pds::config::encrypt_key(&secret_key_bytes) {
Ok(enc) => enc,
Err(e) => {
error!("Error encrypting user key: {:?}", e);
@@ -666,7 +666,7 @@ pub async fn create_account(
.map(|s| s.to_lowercase()),
deactivated_at,
encrypted_key_bytes,
encryption_version: crate::config::ENCRYPTION_VERSION,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
reserved_key_id,
commit_cid: commit_cid_str.clone(),
repo_rev: rev_str.clone(),
@@ -697,7 +697,7 @@ pub async fn create_account(
};
let user_id = create_result.user_id;
if !is_migration && !is_did_web_byod {
if let Err(e) = crate::api::repo::record::sequence_identity_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(
&state,
&did_for_commit,
Some(&handle_typed),
@@ -706,7 +706,7 @@ pub async fn create_account(
{
warn!("Failed to sequence identity event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did_for_commit,
tranquil_db_traits::AccountStatus::Active,
@@ -715,7 +715,7 @@ pub async fn create_account(
{
warn!("Failed to sequence account event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_genesis_commit(
if let Err(e) = tranquil_pds::repo_ops::sequence_genesis_commit(
&state,
&did_for_commit,
&commit_cid,
@@ -726,7 +726,7 @@ pub async fn create_account(
{
warn!("Failed to sequence commit event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_sync_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_sync_event(
&state,
&did_for_commit,
&commit_cid_str,
@@ -740,11 +740,11 @@ pub async fn create_account(
"$type": "app.bsky.actor.profile",
"displayName": input.handle
});
if let Err(e) = crate::api::repo::record::create_record_internal(
if let Err(e) = tranquil_pds::repo_ops::create_record_internal(
&state,
&did_for_commit,
&crate::types::PROFILE_COLLECTION,
&crate::types::PROFILE_RKEY,
&tranquil_pds::types::PROFILE_COLLECTION,
&tranquil_pds::types::PROFILE_RKEY,
&profile_record,
)
.await
@@ -755,14 +755,14 @@ pub async fn create_account(
let hostname = &tranquil_config::get().server.hostname;
if !is_migration {
if let Some(ref recipient) = verification_recipient {
let verification_token = crate::auth::verification_token::generate_signup_token(
let verification_token = tranquil_pds::auth::verification_token::generate_signup_token(
&did_for_commit,
verification_channel,
recipient,
);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -781,9 +781,9 @@ pub async fn create_account(
}
} else if let Some(ref user_email) = email {
let token =
crate::auth::verification_token::generate_migration_token(&did_for_commit, user_email);
let formatted_token = crate::auth::verification_token::format_token_for_display(&token);
if let Err(e) = crate::comms::comms_repo::enqueue_migration_verification(
tranquil_pds::auth::verification_token::generate_migration_token(&did_for_commit, user_email);
let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token);
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_migration_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -797,7 +797,7 @@ pub async fn create_account(
}
}
let access_meta = match crate::auth::create_access_token_with_metadata(&did, &secret_key_bytes)
let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes)
{
Ok(m) => m,
Err(e) => {
@@ -806,7 +806,7 @@ pub async fn create_account(
}
};
let refresh_meta =
match crate::auth::create_refresh_token_with_metadata(&did, &secret_key_bytes) {
match tranquil_pds::auth::create_refresh_token_with_metadata(&did, &secret_key_bytes) {
Ok(m) => m,
Err(e) => {
error!("createAccount: Error creating refresh token: {:?}", e);
@@ -1,12 +1,12 @@
use crate::api::{ApiError, DidResponse, EmptyResponse};
use crate::auth::{Auth, NotTakendown};
use crate::plc::signing_key_to_did_key;
use crate::rate_limit::{
use tranquil_pds::api::{ApiError, DidResponse, EmptyResponse};
use tranquil_pds::auth::{Auth, NotTakendown};
use tranquil_pds::plc::signing_key_to_did_key;
use tranquil_pds::rate_limit::{
HandleUpdateDailyLimit, HandleUpdateLimit, check_user_rate_limit_with_message,
};
use crate::state::AppState;
use crate::types::Handle;
use crate::util::get_header_str;
use tranquil_pds::state::AppState;
use tranquil_pds::types::Handle;
use tranquil_pds::util::get_header_str;
use axum::{
Json,
extract::{Path, Query, State},
@@ -42,7 +42,7 @@ pub async fn resolve_handle(
if handle_str.is_empty() {
return ApiError::InvalidRequest("handle is required".into()).into_response();
}
let cache_key = crate::cache_keys::handle_key(handle_str);
let cache_key = tranquil_pds::cache_keys::handle_key(handle_str);
if let Some(did) = state.cache.get(&cache_key).await {
return DidResponse::response(did).into_response();
}
@@ -61,7 +61,7 @@ pub async fn resolve_handle(
.await;
DidResponse::response(row.did).into_response()
}
Ok(None) => match crate::handle::resolve_handle(handle.as_str()).await {
Ok(None) => match tranquil_pds::handle::resolve_handle(handle.as_str()).await {
Ok(did) => {
let _ = state
.cache
@@ -148,7 +148,7 @@ pub async fn well_known_did(State(state): State<AppState>, headers: HeaderMap) -
"id": did,
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": format!("https://{}", hostname)
}]
}))
@@ -158,7 +158,7 @@ pub async fn well_known_did(State(state): State<AppState>, headers: HeaderMap) -
async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) -> Response {
let encoded_handle = handle.replace(':', "%3A");
let expected_did = format!("did:web:{}", encoded_handle);
let expected_did_typed: crate::types::Did = match expected_did.parse() {
let expected_did_typed: tranquil_pds::types::Did = match expected_did.parse() {
Ok(d) => d,
Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(),
};
@@ -216,7 +216,7 @@ async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) ->
})).collect::<Vec<_>>(),
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
}))
@@ -229,7 +229,7 @@ async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) ->
Err(_) => return ApiError::InternalError(None).into_response(),
};
let key_bytes: Vec<u8> =
match crate::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version) {
match tranquil_pds::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version) {
Ok(k) => k,
Err(_) => {
return ApiError::InternalError(None).into_response();
@@ -269,7 +269,7 @@ async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) ->
}],
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
}))
@@ -351,7 +351,7 @@ pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<Stri
})).collect::<Vec<_>>(),
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
}))
@@ -364,7 +364,7 @@ pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<Stri
Err(_) => return ApiError::InternalError(None).into_response(),
};
let key_bytes: Vec<u8> =
match crate::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version) {
match tranquil_pds::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version) {
Ok(k) => k,
Err(_) => {
return ApiError::InternalError(None).into_response();
@@ -404,7 +404,7 @@ pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<Stri
}],
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
}))
@@ -480,7 +480,7 @@ pub async fn verify_did_web(
let path = parts[3..].join("/");
format!("{}://{}/{}/did.json", scheme, domain, path)
};
let client = crate::api::proxy_client::did_resolution_client();
let client = tranquil_pds::api::proxy_client::did_resolution_client();
let resp = client
.get(&url)
.send()
@@ -503,7 +503,7 @@ pub async fn verify_did_web(
))?;
let pds_endpoint = format!("https://{}", hostname);
let has_valid_service = services.iter().any(|s| {
s["type"] == crate::plc::ServiceType::Pds.as_str() && s["serviceEndpoint"] == pds_endpoint
s["type"] == tranquil_pds::plc::ServiceType::Pds.as_str() && s["serviceEndpoint"] == pds_endpoint
});
if !has_valid_service {
return Err(DidWebVerifyError::PdsNotListed(pds_endpoint));
@@ -600,7 +600,7 @@ pub async fn get_recommended_did_credentials(
verification_methods: VerificationMethods { atproto: did_key },
services: Services {
atproto_pds: AtprotoPds {
service_type: crate::plc::ServiceType::Pds.as_str().to_string(),
service_type: tranquil_pds::plc::ServiceType::Pds.as_str().to_string(),
endpoint: pds_endpoint,
},
},
@@ -619,10 +619,10 @@ pub async fn update_handle(
auth: Auth<NotTakendown>,
Json(input): Json<UpdateHandleInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_identity_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_identity_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::IdentityAttr::Handle,
tranquil_pds::oauth::scopes::IdentityAttr::Handle,
) {
return Ok(e);
}
@@ -672,7 +672,7 @@ pub async fn update_handle(
"Handle segment cannot start or end with hyphen".into(),
)));
}
if crate::moderation::has_explicit_slur(&new_handle) {
if tranquil_pds::moderation::has_explicit_slur(&new_handle) {
return Err(ApiError::InvalidHandle(Some(
"Inappropriate language in handle".into(),
)));
@@ -704,7 +704,7 @@ pub async fn update_handle(
Err(_) => return Err(ApiError::InvalidHandle(None)),
};
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&handle_typed))
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed))
.await
{
warn!("Failed to sequence identity event for handle update: {}", e);
@@ -730,19 +730,19 @@ pub async fn update_handle(
Err(_) => return Err(ApiError::InvalidHandle(None)),
};
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&handle_typed))
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed))
.await
{
warn!("Failed to sequence identity event for handle update: {}", e);
}
return Ok(EmptyResponse::ok().into_response());
}
match crate::handle::verify_handle_ownership(&new_handle, &did).await {
match tranquil_pds::handle::verify_handle_ownership(&new_handle, &did).await {
Ok(()) => {}
Err(crate::handle::HandleResolutionError::NotFound) => {
Err(tranquil_pds::handle::HandleResolutionError::NotFound) => {
return Err(ApiError::HandleNotAvailable(None));
}
Err(crate::handle::HandleResolutionError::DidMismatch { expected, actual }) => {
Err(tranquil_pds::handle::HandleResolutionError::DidMismatch { expected, actual }) => {
return Err(ApiError::HandleNotAvailable(Some(format!(
"Handle points to different DID. Expected {}, got {}",
expected, actual
@@ -781,15 +781,15 @@ pub async fn update_handle(
if !current_handle.is_empty() {
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&current_handle))
.delete(&tranquil_pds::cache_keys::handle_key(&current_handle))
.await;
}
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&handle))
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
.await;
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did, Some(&handle_typed)).await
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed)).await
{
warn!("Failed to sequence identity event for handle update: {}", e);
}
@@ -801,7 +801,7 @@ pub async fn update_handle(
pub async fn update_plc_handle(
state: &AppState,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
new_handle: &Handle,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !did.as_str().starts_with("did:plc:") {
@@ -811,20 +811,20 @@ pub async fn update_plc_handle(
Some(r) => r,
None => return Ok(()),
};
let key_bytes = crate::config::decrypt_key(&user_row.key_bytes, user_row.encryption_version)?;
let key_bytes = tranquil_pds::config::decrypt_key(&user_row.key_bytes, user_row.encryption_version)?;
let signing_key = k256::ecdsa::SigningKey::from_slice(&key_bytes)?;
let plc_client = crate::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
let last_op = plc_client.get_last_op(did).await?;
let new_also_known_as = vec![format!("at://{}", new_handle)];
let update_op =
crate::plc::create_update_op(&last_op, None, None, Some(new_also_known_as), None)?;
let signed_op = crate::plc::sign_operation(&update_op, &signing_key)?;
tranquil_pds::plc::create_update_op(&last_op, None, None, Some(new_also_known_as), None)?;
let signed_op = tranquil_pds::plc::sign_operation(&update_op, &signing_key)?;
plc_client.send_operation(did, &signed_op).await?;
Ok(())
}
pub async fn well_known_atproto_did(State(state): State<AppState>, headers: HeaderMap) -> Response {
let host = match crate::util::get_header_str(&headers, http::header::HOST) {
let host = match tranquil_pds::util::get_header_str(&headers, http::header::HOST) {
Some(h) => h,
None => return (StatusCode::BAD_REQUEST, "Missing host header").into_response(),
};
@@ -1,5 +1,5 @@
use crate::rate_limit::{HandleVerificationLimit, RateLimited};
use crate::types::{Did, Handle};
use tranquil_pds::rate_limit::{HandleVerificationLimit, RateLimited};
use tranquil_pds::types::{Did, Handle};
use axum::{
Json,
response::{IntoResponse, Response},
@@ -29,7 +29,7 @@ pub async fn verify_handle_ownership(
let handle_str = input.handle.as_str();
let did_str = input.did.as_str();
let dns_mismatch = match crate::handle::resolve_handle_dns(handle_str).await {
let dns_mismatch = match tranquil_pds::handle::resolve_handle_dns(handle_str).await {
Ok(did) if did == did_str => {
return Json(VerifyHandleOwnershipOutput {
verified: true,
@@ -45,7 +45,7 @@ pub async fn verify_handle_ownership(
Err(_) => None,
};
match crate::handle::resolve_handle_http(handle_str).await {
match tranquil_pds::handle::resolve_handle_http(handle_str).await {
Ok(did) if did == did_str => Json(VerifyHandleOwnershipOutput {
verified: true,
method: Some("http".to_string()),
@@ -1,7 +1,7 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Auth, Permissive};
use crate::state::AppState;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Auth, Permissive};
use tranquil_pds::state::AppState;
use axum::{
extract::State,
response::{IntoResponse, Response},
@@ -10,17 +10,17 @@ use chrono::{Duration, Utc};
use tracing::{info, warn};
fn generate_plc_token() -> String {
crate::util::generate_token_code()
tranquil_pds::util::generate_token_code()
}
pub async fn request_plc_operation_signature(
State(state): State<AppState>,
auth: Auth<Permissive>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_identity_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_identity_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::IdentityAttr::Wildcard,
tranquil_pds::oauth::scopes::IdentityAttr::Wildcard,
) {
return Ok(e);
}
@@ -41,7 +41,7 @@ pub async fn request_plc_operation_signature(
.log_db_err("creating PLC token")?;
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_plc_operation(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_plc_operation(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -1,9 +1,9 @@
use crate::api::ApiError;
use crate::api::error::DbResultExt;
use crate::auth::{Auth, Permissive};
use crate::circuit_breaker::with_circuit_breaker;
use crate::plc::{PlcClient, PlcError, PlcService, ServiceType, create_update_op, sign_operation};
use crate::state::AppState;
use tranquil_pds::api::ApiError;
use tranquil_pds::api::error::DbResultExt;
use tranquil_pds::auth::{Auth, Permissive};
use tranquil_pds::circuit_breaker::with_circuit_breaker;
use tranquil_pds::plc::{PlcClient, PlcError, PlcService, ServiceType, create_update_op, sign_operation};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -44,10 +44,10 @@ pub async fn sign_plc_operation(
auth: Auth<Permissive>,
Json(input): Json<SignPlcOperationInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_identity_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_identity_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::IdentityAttr::Wildcard,
tranquil_pds::oauth::scopes::IdentityAttr::Wildcard,
) {
return Ok(e);
}
@@ -86,7 +86,7 @@ pub async fn sign_plc_operation(
.log_db_err("fetching user key")?
.ok_or_else(|| ApiError::InternalError(Some("User signing key not found".into())))?;
let key_bytes = crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
let key_bytes = tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
.map_err(|e| {
error!("Failed to decrypt user key: {}", e);
ApiError::InternalError(None)
@@ -1,9 +1,9 @@
use crate::api::error::DbResultExt;
use crate::api::{ApiError, EmptyResponse};
use crate::auth::{Auth, Permissive};
use crate::circuit_breaker::with_circuit_breaker;
use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation};
use crate::state::AppState;
use tranquil_pds::api::error::DbResultExt;
use tranquil_pds::api::{ApiError, EmptyResponse};
use tranquil_pds::auth::{Auth, Permissive};
use tranquil_pds::circuit_breaker::with_circuit_breaker;
use tranquil_pds::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -24,10 +24,10 @@ pub async fn submit_plc_operation(
auth: Auth<Permissive>,
Json(input): Json<SubmitPlcOperationInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_identity_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_identity_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::IdentityAttr::Wildcard,
tranquil_pds::oauth::scopes::IdentityAttr::Wildcard,
) {
return Ok(e);
}
@@ -57,7 +57,7 @@ pub async fn submit_plc_operation(
.log_db_err("fetching user key")?
.ok_or_else(|| ApiError::InternalError(Some("User signing key not found".into())))?;
let key_bytes = crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
let key_bytes = tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
.map_err(|e| {
error!("Failed to decrypt user key: {}", e);
ApiError::InternalError(None)
@@ -89,7 +89,7 @@ pub async fn submit_plc_operation(
{
let service_type = pds.get("type").and_then(|v| v.as_str());
let endpoint = pds.get("endpoint").and_then(|v| v.as_str());
if service_type != Some(crate::plc::ServiceType::Pds.as_str()) {
if service_type != Some(tranquil_pds::plc::ServiceType::Pds.as_str()) {
return Err(ApiError::InvalidRequest(
"Incorrect type on atproto_pds service".into(),
));
@@ -147,15 +147,15 @@ pub async fn submit_plc_operation(
}
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&user.handle))
.delete(&tranquil_pds::cache_keys::handle_key(&user.handle))
.await;
let _ = state
.cache
.delete(&crate::cache_keys::plc_doc_key(did))
.delete(&tranquil_pds::cache_keys::plc_doc_key(did))
.await;
let _ = state
.cache
.delete(&crate::cache_keys::plc_data_key(did))
.delete(&tranquil_pds::cache_keys::plc_data_key(did))
.await;
if state.did_resolver.refresh_did(did).await.is_none() {
warn!(did = %did, "Failed to refresh DID cache after PLC update");
+273
View File
@@ -0,0 +1,273 @@
pub mod actor;
pub mod admin;
pub mod age_assurance;
pub mod backup;
pub mod delegation;
pub mod discord_webhook;
pub mod identity;
pub mod moderation;
pub mod notification_prefs;
pub mod repo;
pub mod server;
pub mod telegram_webhook;
pub mod temp;
pub mod verification;
use tranquil_pds::state::AppState;
pub fn api_routes() -> axum::Router<AppState> {
use axum::routing::{get, post};
axum::Router::new()
.route("/_health", get(server::health))
.route(
"/com.atproto.server.describeServer",
get(server::describe_server),
)
.route(
"/com.atproto.server.createAccount",
post(identity::create_account),
)
.route(
"/com.atproto.server.createSession",
post(server::create_session),
)
.route(
"/com.atproto.server.getSession",
get(server::get_session),
)
.route("/_account.listSessions", get(server::list_sessions))
.route("/_account.revokeSession", post(server::revoke_session))
.route(
"/_account.revokeAllSessions",
post(server::revoke_all_sessions),
)
.route(
"/com.atproto.server.deleteSession",
post(server::delete_session),
)
.route(
"/com.atproto.server.refreshSession",
post(server::refresh_session),
)
.route(
"/com.atproto.server.confirmSignup",
post(server::confirm_signup),
)
.route(
"/com.atproto.server.resendVerification",
post(server::resend_verification),
)
.route(
"/com.atproto.server.getServiceAuth",
get(server::get_service_auth),
)
.route(
"/com.atproto.identity.resolveHandle",
get(identity::resolve_handle),
)
.route(
"/com.atproto.repo.createRecord",
post(repo::create_record),
)
.route("/com.atproto.repo.putRecord", post(repo::put_record))
.route("/com.atproto.repo.getRecord", get(repo::get_record))
.route(
"/com.atproto.repo.deleteRecord",
post(repo::delete_record),
)
.route(
"/com.atproto.repo.listRecords",
get(repo::list_records),
)
.route(
"/com.atproto.repo.describeRepo",
get(repo::describe_repo),
)
.route("/com.atproto.repo.uploadBlob", post(repo::upload_blob))
.route(
"/com.atproto.repo.applyWrites",
post(repo::apply_writes),
)
.route(
"/com.atproto.server.checkAccountStatus",
get(server::check_account_status),
)
.route(
"/com.atproto.identity.getRecommendedDidCredentials",
get(identity::get_recommended_did_credentials),
)
.route(
"/com.atproto.repo.listMissingBlobs",
get(repo::list_missing_blobs),
)
.route(
"/com.atproto.moderation.createReport",
post(moderation::create_report),
)
.route(
"/com.atproto.admin.getAccountInfo",
get(admin::get_account_info),
)
.route(
"/com.atproto.admin.getAccountInfos",
get(admin::get_account_infos),
)
.route(
"/com.atproto.admin.searchAccounts",
get(admin::search_accounts),
)
.route(
"/com.atproto.server.activateAccount",
post(server::activate_account),
)
.route(
"/com.atproto.server.deactivateAccount",
post(server::deactivate_account),
)
.route(
"/com.atproto.server.requestAccountDelete",
post(server::request_account_delete),
)
.route(
"/com.atproto.server.deleteAccount",
post(server::delete_account),
)
.route(
"/com.atproto.server.requestPasswordReset",
post(server::request_password_reset),
)
.route(
"/com.atproto.server.resetPassword",
post(server::reset_password),
)
.route("/_account.changePassword", post(server::change_password))
.route("/_account.removePassword", post(server::remove_password))
.route("/_account.setPassword", post(server::set_password))
.route("/_account.getPasswordStatus", get(server::get_password_status))
.route("/_account.getReauthStatus", get(server::get_reauth_status))
.route("/_account.reauthPassword", post(server::reauth_password))
.route("/_account.reauthTotp", post(server::reauth_totp))
.route("/_account.reauthPasskeyStart", post(server::reauth_passkey_start))
.route("/_account.reauthPasskeyFinish", post(server::reauth_passkey_finish))
.route("/_account.getLegacyLoginPreference", get(server::get_legacy_login_preference))
.route("/_account.updateLegacyLoginPreference", post(server::update_legacy_login_preference))
.route("/_account.updateLocale", post(server::update_locale))
.route("/_account.listTrustedDevices", get(server::list_trusted_devices))
.route("/_account.revokeTrustedDevice", post(server::revoke_trusted_device))
.route("/_account.updateTrustedDevice", post(server::update_trusted_device))
.route("/_account.createPasskeyAccount", post(server::create_passkey_account))
.route("/_account.startPasskeyRegistrationForSetup", post(server::start_passkey_registration_for_setup))
.route("/_account.completePasskeySetup", post(server::complete_passkey_setup))
.route("/_account.requestPasskeyRecovery", post(server::request_passkey_recovery))
.route("/_account.recoverPasskeyAccount", post(server::recover_passkey_account))
.route("/_account.updateDidDocument", post(server::update_did_document))
.route("/_account.getDidDocument", get(server::get_did_document))
.route("/com.atproto.server.requestEmailUpdate", post(server::request_email_update))
.route("/_checkEmailVerified", post(server::check_email_verified))
.route("/_checkChannelVerified", post(server::check_channel_verified))
.route("/com.atproto.server.confirmEmail", post(server::confirm_email))
.route("/com.atproto.server.updateEmail", post(server::update_email))
.route("/_account.authorizeEmailUpdate", get(server::authorize_email_update))
.route("/_account.checkEmailUpdateStatus", get(server::check_email_update_status))
.route("/_account.checkEmailInUse", post(server::check_email_in_use))
.route("/_account.checkCommsChannelInUse", post(server::check_comms_channel_in_use))
.route("/com.atproto.server.reserveSigningKey", post(server::reserve_signing_key))
.route("/com.atproto.server.verifyMigrationEmail", post(server::verify_migration_email))
.route("/com.atproto.server.resendMigrationVerification", post(server::resend_migration_verification))
.route("/com.atproto.identity.updateHandle", post(identity::update_handle))
.route("/com.atproto.identity.requestPlcOperationSignature", post(identity::request_plc_operation_signature))
.route("/com.atproto.identity.signPlcOperation", post(identity::sign_plc_operation))
.route("/com.atproto.identity.submitPlcOperation", post(identity::submit_plc_operation))
.route("/_identity.verifyHandleOwnership", post(identity::verify_handle_ownership))
.route("/com.atproto.repo.importRepo", post(repo::import_repo))
.route("/com.atproto.admin.deleteAccount", post(admin::delete_account))
.route("/com.atproto.admin.updateAccountEmail", post(admin::update_account_email))
.route("/com.atproto.admin.updateAccountHandle", post(admin::update_account_handle))
.route("/com.atproto.admin.updateAccountPassword", post(admin::update_account_password))
.route("/com.atproto.server.listAppPasswords", get(server::list_app_passwords))
.route("/com.atproto.server.createAppPassword", post(server::create_app_password))
.route("/com.atproto.server.revokeAppPassword", post(server::revoke_app_password))
.route("/com.atproto.server.createInviteCode", post(server::create_invite_code))
.route("/com.atproto.server.createInviteCodes", post(server::create_invite_codes))
.route("/com.atproto.server.getAccountInviteCodes", get(server::get_account_invite_codes))
.route("/com.atproto.server.createTotpSecret", post(server::create_totp_secret))
.route("/com.atproto.server.enableTotp", post(server::enable_totp))
.route("/com.atproto.server.disableTotp", post(server::disable_totp))
.route("/com.atproto.server.getTotpStatus", get(server::get_totp_status))
.route("/com.atproto.server.regenerateBackupCodes", post(server::regenerate_backup_codes))
.route("/com.atproto.server.startPasskeyRegistration", post(server::start_passkey_registration))
.route("/com.atproto.server.finishPasskeyRegistration", post(server::finish_passkey_registration))
.route("/com.atproto.server.listPasskeys", get(server::list_passkeys))
.route("/com.atproto.server.deletePasskey", post(server::delete_passkey))
.route("/com.atproto.server.updatePasskey", post(server::update_passkey))
.route("/com.atproto.admin.getInviteCodes", get(admin::get_invite_codes))
.route("/_admin.getServerStats", get(admin::get_server_stats))
.route("/_server.getConfig", get(admin::get_server_config))
.route("/_admin.updateServerConfig", post(admin::update_server_config))
.route("/com.atproto.admin.disableAccountInvites", post(admin::disable_account_invites))
.route("/com.atproto.admin.enableAccountInvites", post(admin::enable_account_invites))
.route("/com.atproto.admin.disableInviteCodes", post(admin::disable_invite_codes))
.route("/com.atproto.admin.getSubjectStatus", get(admin::get_subject_status))
.route("/com.atproto.admin.updateSubjectStatus", post(admin::update_subject_status))
.route("/com.atproto.admin.sendEmail", post(admin::send_email))
.route("/app.bsky.actor.getPreferences", get(actor::get_preferences))
.route("/app.bsky.actor.putPreferences", post(actor::put_preferences))
.route("/com.atproto.temp.checkSignupQueue", get(temp::check_signup_queue))
.route("/com.atproto.temp.dereferenceScope", post(temp::dereference_scope))
.route("/_account.getNotificationPrefs", get(notification_prefs::get_notification_prefs))
.route("/_account.updateNotificationPrefs", post(notification_prefs::update_notification_prefs))
.route("/_account.getNotificationHistory", get(notification_prefs::get_notification_history))
.route("/_account.confirmChannelVerification", post(verification::confirm_channel_verification))
.route("/_account.verifyToken", post(server::verify_token))
.route("/_delegation.listControllers", get(delegation::list_controllers))
.route("/_delegation.addController", post(delegation::add_controller))
.route("/_delegation.removeController", post(delegation::remove_controller))
.route("/_delegation.updateControllerScopes", post(delegation::update_controller_scopes))
.route("/_delegation.listControlledAccounts", get(delegation::list_controlled_accounts))
.route("/_delegation.getAuditLog", get(delegation::get_audit_log))
.route("/_delegation.getScopePresets", get(delegation::get_scope_presets))
.route("/_delegation.createDelegatedAccount", post(delegation::create_delegated_account))
.route("/_backup.listBackups", get(backup::list_backups))
.route("/_backup.getBackup", get(backup::get_backup))
.route("/_backup.createBackup", post(backup::create_backup))
.route("/_backup.deleteBackup", post(backup::delete_backup))
.route("/_backup.setEnabled", post(backup::set_backup_enabled))
.route("/_backup.exportBlobs", get(backup::export_blobs))
.route("/app.bsky.ageassurance.getState", get(age_assurance::get_state))
.route("/app.bsky.unspecced.getAgeAssuranceState", get(age_assurance::get_age_assurance_state))
}
pub fn well_known_api_routes() -> axum::Router<AppState> {
use axum::routing::get;
axum::Router::new()
.route("/did.json", get(identity::well_known_did))
.route("/atproto-did", get(identity::well_known_atproto_did))
}
pub fn webhook_routes() -> axum::Router<AppState> {
use axum::{extract::DefaultBodyLimit, routing::post};
axum::Router::new()
.route(
"/webhook/telegram",
post(telegram_webhook::handle_telegram_webhook)
.layer(DefaultBodyLimit::max(64 * 1024)),
)
.route(
"/webhook/discord",
post(discord_webhook::handle_discord_webhook)
.layer(DefaultBodyLimit::max(64 * 1024)),
)
}
pub fn misc_routes() -> axum::Router<AppState> {
use axum::routing::get;
axum::Router::new()
.route("/health", get(server::health))
.route("/robots.txt", get(server::robots_txt))
.route("/favicon.ico", get(server::get_logo))
.route("/u/{handle}/did.json", get(identity::user_did_doc))
}
@@ -1,7 +1,7 @@
use crate::api::ApiError;
use crate::api::proxy_client::{is_ssrf_safe, proxy_client};
use crate::auth::{AnyUser, Auth};
use crate::state::AppState;
use tranquil_pds::api::ApiError;
use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client};
use tranquil_pds::auth::{AnyUser, Auth};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -94,7 +94,7 @@ pub async fn create_report(
async fn proxy_to_report_service(
state: &AppState,
auth_user: &crate::auth::AuthenticatedUser,
auth_user: &tranquil_pds::auth::AuthenticatedUser,
service_url: &str,
service_did: &str,
input: &CreateReportInput,
@@ -109,7 +109,7 @@ async fn proxy_to_report_service(
Some(kb) => kb.clone(),
None => match state.user_repo.get_with_key_by_did(&auth_user.did).await {
Ok(Some(user_with_key)) => {
match crate::config::decrypt_key(
match tranquil_pds::config::decrypt_key(
&user_with_key.key_bytes,
user_with_key.encryption_version,
) {
@@ -135,7 +135,7 @@ async fn proxy_to_report_service(
},
};
let service_token = match crate::auth::create_service_token(
let service_token = match tranquil_pds::auth::create_service_token(
&auth_user.did,
service_did,
"com.atproto.moderation.createReport",
@@ -211,7 +211,7 @@ async fn proxy_to_report_service(
async fn create_report_locally(
state: &AppState,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
is_takendown: bool,
input: CreateReportInput,
) -> Response {
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::auth::{Active, Auth};
use crate::state::AppState;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -142,14 +142,14 @@ pub async fn request_channel_verification(
handle: Option<&str>,
) -> Result<String, ApiError> {
let token =
crate::auth::verification_token::generate_channel_update_token(did, channel, identifier);
let formatted_token = crate::auth::verification_token::format_token_for_display(&token);
tranquil_pds::auth::verification_token::generate_channel_update_token(did, channel, identifier);
let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token);
match channel {
CommsChannel::Email => {
let hostname = &tranquil_config::get().server.hostname;
let handle_str = handle.unwrap_or("user");
crate::comms::comms_repo::enqueue_email_update(
tranquil_pds::comms::comms_repo::enqueue_email_update(
state.infra_repo.as_ref(),
user_id,
identifier,
@@ -183,12 +183,12 @@ pub async fn request_channel_verification(
.as_ref()
.and_then(|p| p.preferred_locale.as_deref())
.unwrap_or("en");
let strings = crate::comms::get_strings(locale);
let body = crate::comms::format_message(
let strings = tranquil_pds::comms::get_strings(locale);
let body = tranquil_pds::comms::format_message(
strings.channel_verification_body,
&[("code", &formatted_token), ("verify_link", &verify_link)],
);
let subject = crate::comms::format_message(
let subject = tranquil_pds::comms::format_message(
strings.channel_verification_subject,
&[("hostname", hostname)],
);
@@ -277,7 +277,7 @@ pub async fn update_notification_prefs(
return Err(ApiError::InvalidRequest("Email cannot be empty".into()));
}
if !crate::api::validation::is_valid_email(&email_clean) {
if !tranquil_pds::api::validation::is_valid_email(&email_clean) {
return Err(ApiError::InvalidEmail);
}
@@ -310,7 +310,7 @@ pub async fn update_notification_prefs(
.await
.map_err(|e| ApiError::InternalError(Some(format!("Database error: {}", e))))?;
info!(did = %auth.did, "Cleared Discord");
} else if !crate::api::validation::is_valid_discord_username(&discord_clean) {
} else if !tranquil_pds::api::validation::is_valid_discord_username(&discord_clean) {
return Err(ApiError::InvalidRequest(
"Invalid Discord username. Must be 2-32 lowercase characters (letters, numbers, underscores, periods)"
.into(),
@@ -340,7 +340,7 @@ pub async fn update_notification_prefs(
.await
.map_err(|e| ApiError::InternalError(Some(format!("Database error: {}", e))))?;
info!(did = %auth.did, "Cleared Telegram username");
} else if !crate::api::validation::is_valid_telegram_username(telegram_clean) {
} else if !tranquil_pds::api::validation::is_valid_telegram_username(telegram_clean) {
return Err(ApiError::InvalidRequest(
"Invalid Telegram username. Must be 5-32 characters, alphanumeric or underscore"
.into(),
@@ -370,7 +370,7 @@ pub async fn update_notification_prefs(
.await
.map_err(|e| ApiError::InternalError(Some(format!("Database error: {}", e))))?;
info!(did = %auth.did, "Cleared Signal username");
} else if !crate::comms::is_valid_signal_username(&signal_clean) {
} else if !tranquil_pds::comms::is_valid_signal_username(&signal_clean) {
return Err(ApiError::InvalidRequest(
"Invalid Signal username. Must be 3-32 characters followed by .XX (e.g. username.01)"
.into(),
@@ -1,9 +1,9 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope};
use crate::delegation::DelegationActionType;
use crate::state::AppState;
use crate::types::{CidLink, Did};
use crate::util::get_header_str;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope};
use tranquil_pds::delegation::DelegationActionType;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{CidLink, Did};
use tranquil_pds::util::get_header_str;
use axum::body::Body;
use axum::{
Json,
@@ -1,11 +1,11 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::api::repo::record::create_signed_commit;
use crate::auth::{Auth, NotTakendown};
use crate::state::AppState;
use crate::sync::import::{ImportError, apply_import, parse_car};
use crate::sync::verify::CarVerifier;
use crate::types::Did;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::repo_ops::create_signed_commit;
use tranquil_pds::auth::{Auth, NotTakendown};
use tranquil_pds::state::AppState;
use tranquil_pds::sync::import::{ImportError, apply_import, parse_car};
use tranquil_pds::sync::verify::CarVerifier;
use tranquil_pds::types::Did;
use axum::{
body::Bytes,
extract::State,
@@ -121,7 +121,7 @@ pub async fn import_repo(
verified.rev, verified.data_cid
);
}
Err(crate::sync::verify::VerifyError::DidMismatch {
Err(tranquil_pds::sync::verify::VerifyError::DidMismatch {
commit_did,
expected_did,
}) => {
@@ -130,7 +130,7 @@ pub async fn import_repo(
commit_did, expected_did
)));
}
Err(crate::sync::verify::VerifyError::MstValidationFailed(msg)) => {
Err(tranquil_pds::sync::verify::VerifyError::MstValidationFailed(msg)) => {
return Err(ApiError::InvalidRequest(format!(
"MST validation failed: {}",
msg
@@ -154,7 +154,7 @@ pub async fn import_repo(
verified.rev, verified.data_cid
);
}
Err(crate::sync::verify::VerifyError::DidMismatch {
Err(tranquil_pds::sync::verify::VerifyError::DidMismatch {
commit_did,
expected_did,
}) => {
@@ -163,24 +163,24 @@ pub async fn import_repo(
commit_did, expected_did
)));
}
Err(crate::sync::verify::VerifyError::InvalidSignature) => {
Err(tranquil_pds::sync::verify::VerifyError::InvalidSignature) => {
return Err(ApiError::InvalidRequest(
"CAR file commit signature verification failed".into(),
));
}
Err(crate::sync::verify::VerifyError::DidResolutionFailed(msg)) => {
Err(tranquil_pds::sync::verify::VerifyError::DidResolutionFailed(msg)) => {
warn!("DID resolution failed during import verification: {}", msg);
return Err(ApiError::InvalidRequest(format!(
"Failed to verify DID: {}",
msg
)));
}
Err(crate::sync::verify::VerifyError::NoSigningKey) => {
Err(tranquil_pds::sync::verify::VerifyError::NoSigningKey) => {
return Err(ApiError::InvalidRequest(
"DID document does not contain a signing key".into(),
));
}
Err(crate::sync::verify::VerifyError::MstValidationFailed(msg)) => {
Err(tranquil_pds::sync::verify::VerifyError::MstValidationFailed(msg)) => {
return Err(ApiError::InvalidRequest(format!(
"MST validation failed: {}",
msg
@@ -264,7 +264,7 @@ pub async fn import_repo(
ApiError::InternalError(Some("Signing key not found".into()))
})?;
let key_bytes =
crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
.map_err(|e| {
error!("Failed to decrypt signing key: {}", e);
ApiError::InternalError(None)
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::state::AppState;
use crate::types::AtIdentifier;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::state::AppState;
use tranquil_pds::types::AtIdentifier;
use axum::{
Json,
extract::{Query, State},
@@ -20,7 +20,7 @@ pub async fn describe_repo(
) -> Response {
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
let user_row = if input.repo.is_did() {
let did: crate::types::Did = match input.repo.as_str().parse() {
let did: tranquil_pds::types::Did = match input.repo.as_str().parse() {
Ok(d) => d,
Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(),
};
@@ -36,7 +36,7 @@ pub async fn describe_repo(
} else {
repo_str.to_string()
};
let handle: crate::types::Handle = match handle_str.parse() {
let handle: tranquil_pds::types::Handle = match handle_str.parse() {
Ok(h) => h,
Err(_) => {
return ApiError::InvalidRequest("Invalid handle format".into()).into_response();
@@ -1,17 +1,17 @@
use super::validation::validate_record_with_status;
use super::validation_mode::{ValidationMode, deserialize_validation_mode};
use crate::api::error::ApiError;
use crate::api::repo::record::utils::{CommitParams, RecordOp, commit_and_log, extract_blob_cids};
use crate::auth::{
use tranquil_pds::api::error::ApiError;
use crate::repo::record::utils::{CommitParams, RecordOp, commit_and_log, extract_blob_cids};
use tranquil_pds::auth::{
Active, Auth, WriteOpKind, require_not_migrated, require_verified_or_delegated,
verify_batch_write_scopes,
};
use crate::cid_types::CommitCid;
use crate::delegation::DelegationActionType;
use crate::repo::tracking::TrackingBlockStore;
use crate::state::AppState;
use crate::types::{AtIdentifier, AtUri, Did, Nsid, Rkey};
use crate::validation::ValidationStatus;
use tranquil_pds::cid_types::CommitCid;
use tranquil_pds::delegation::DelegationActionType;
use tranquil_pds::repo::tracking::TrackingBlockStore;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey};
use tranquil_pds::validation::ValidationStatus;
use axum::{
Json,
extract::State,
@@ -74,7 +74,7 @@ async fn process_single_write(
};
all_blob_cids.extend(extract_blob_cids(value));
let rkey = rkey.clone().unwrap_or_else(Rkey::generate);
let record_ipld = crate::util::json_to_ipld(value);
let record_ipld = tranquil_pds::util::json_to_ipld(value);
let record_bytes = serde_ipld_dagcbor::to_vec(&record_ipld).map_err(|_| {
ApiError::InvalidRecord("Failed to serialize record".into()).into_response()
})?;
@@ -126,7 +126,7 @@ async fn process_single_write(
}
};
all_blob_cids.extend(extract_blob_cids(value));
let record_ipld = crate::util::json_to_ipld(value);
let record_ipld = tranquil_pds::util::json_to_ipld(value);
let record_bytes = serde_ipld_dagcbor::to_vec(&record_ipld).map_err(|_| {
ApiError::InvalidRecord("Failed to serialize record".into()).into_response()
})?;
@@ -1,14 +1,14 @@
use crate::api::error::ApiError;
use crate::api::repo::record::utils::{
use tranquil_pds::api::error::ApiError;
use crate::repo::record::utils::{
CommitError, CommitParams, RecordOp, commit_and_log, get_current_root_cid,
};
use crate::api::repo::record::write::{CommitInfo, prepare_repo_write};
use crate::auth::{Active, Auth, VerifyScope};
use crate::cid_types::CommitCid;
use crate::delegation::DelegationActionType;
use crate::repo::tracking::TrackingBlockStore;
use crate::state::AppState;
use crate::types::{AtIdentifier, AtUri, Nsid, Rkey};
use crate::repo::record::write::{CommitInfo, prepare_repo_write};
use tranquil_pds::auth::{Active, Auth, VerifyScope};
use tranquil_pds::cid_types::CommitCid;
use tranquil_pds::delegation::DelegationActionType;
use tranquil_pds::repo::tracking::TrackingBlockStore;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{AtIdentifier, AtUri, Nsid, Rkey};
use axum::{
Json,
extract::State,
@@ -45,7 +45,7 @@ pub async fn delete_record(
State(state): State<AppState>,
auth: Auth<Active>,
Json(input): Json<DeleteRecordInput>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let scope_proof = match auth.verify_repo_delete(&input.collection) {
Ok(proof) => proof,
Err(e) => return Ok(e.into_response()),
@@ -229,7 +229,7 @@ pub async fn delete_record(
.into_response())
}
use crate::types::Did;
use tranquil_pds::types::Did;
use uuid::Uuid;
pub async fn delete_record_internal(
@@ -1,7 +1,7 @@
use super::pagination::{PaginationDirection, deserialize_pagination_direction};
use crate::api::error::ApiError;
use crate::state::AppState;
use crate::types::{AtIdentifier, Nsid, Rkey};
use tranquil_pds::api::error::ApiError;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{AtIdentifier, Nsid, Rkey};
use axum::{
Json,
extract::{Query, State},
@@ -61,7 +61,7 @@ pub async fn get_record(
) -> Response {
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
let user_id_opt = if input.repo.is_did() {
let did: crate::types::Did = match input.repo.as_str().parse() {
let did: tranquil_pds::types::Did = match input.repo.as_str().parse() {
Ok(d) => d,
Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(),
};
@@ -73,7 +73,7 @@ pub async fn get_record(
} else {
repo_str.to_string()
};
let handle: crate::types::Handle = match handle_str.parse() {
let handle: tranquil_pds::types::Handle = match handle_str.parse() {
Ok(h) => h,
Err(_) => {
return ApiError::InvalidRequest("Invalid handle format".into()).into_response();
@@ -160,7 +160,7 @@ pub async fn list_records(
) -> Response {
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
let user_id_opt = if input.repo.is_did() {
let did: crate::types::Did = match input.repo.as_str().parse() {
let did: tranquil_pds::types::Did = match input.repo.as_str().parse() {
Ok(d) => d,
Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(),
};
@@ -172,7 +172,7 @@ pub async fn list_records(
} else {
repo_str.to_string()
};
let handle: crate::types::Handle = match handle_str.parse() {
let handle: tranquil_pds::types::Handle = match handle_str.parse() {
Ok(h) => h,
Err(_) => {
return ApiError::InvalidRequest("Invalid handle format".into()).into_response();
@@ -198,7 +198,7 @@ pub async fn list_records(
let cursor_rkey = input
.cursor
.as_ref()
.and_then(|c| c.parse::<crate::types::Rkey>().ok());
.and_then(|c| c.parse::<tranquil_pds::types::Rkey>().ok());
let rows = match state
.repo_repo
.list_records(
@@ -0,0 +1 @@
pub use tranquil_pds::repo_ops::*;
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::types::{Nsid, Rkey};
use crate::validation::{RecordValidator, ValidationError, ValidationStatus};
use tranquil_pds::api::error::ApiError;
use tranquil_pds::types::{Nsid, Rkey};
use tranquil_pds::validation::{RecordValidator, ValidationError, ValidationStatus};
use axum::response::Response;
pub async fn validate_record_with_status(
@@ -1,20 +1,20 @@
use super::validation::validate_record_with_status;
use super::validation_mode::{ValidationMode, deserialize_validation_mode};
use crate::api::error::ApiError;
use crate::api::repo::record::utils::{
use tranquil_pds::api::error::ApiError;
use crate::repo::record::utils::{
CommitParams, RecordOp, commit_and_log, extract_backlinks, extract_blob_cids,
get_current_root_cid,
};
use crate::auth::{
use tranquil_pds::auth::{
Active, Auth, AuthSource, RepoScopeAction, ScopeVerified, VerifyScope, require_not_migrated,
require_verified_or_delegated,
};
use crate::cid_types::CommitCid;
use crate::delegation::DelegationActionType;
use crate::repo::tracking::TrackingBlockStore;
use crate::state::AppState;
use crate::types::{AtIdentifier, AtUri, Did, Nsid, Rkey};
use crate::validation::ValidationStatus;
use tranquil_pds::cid_types::CommitCid;
use tranquil_pds::delegation::DelegationActionType;
use tranquil_pds::repo::tracking::TrackingBlockStore;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey};
use tranquil_pds::validation::ValidationStatus;
use axum::{
Json,
extract::State,
@@ -104,7 +104,7 @@ pub async fn create_record(
State(state): State<AppState>,
auth: Auth<Active>,
Json(input): Json<CreateRecordInput>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let scope_proof = match auth.verify_repo_create(&input.collection) {
Ok(proof) => proof,
Err(e) => return Ok(e.into_response()),
@@ -232,7 +232,7 @@ pub async fn create_record(
}
}
let record_ipld = crate::util::json_to_ipld(&input.record);
let record_ipld = tranquil_pds::util::json_to_ipld(&input.record);
let mut record_bytes = Vec::new();
if serde_ipld_dagcbor::to_writer(&mut record_bytes, &record_ipld).is_err() {
return Ok(ApiError::InvalidRecord("Failed to serialize record".into()).into_response());
@@ -408,7 +408,7 @@ pub async fn put_record(
State(state): State<AppState>,
auth: Auth<Active>,
Json(input): Json<PutRecordInput>,
) -> Result<Response, crate::api::error::ApiError> {
) -> Result<Response, tranquil_pds::api::error::ApiError> {
let upsert_proof = match auth.verify_repo_upsert(&input.collection) {
Ok(proof) => proof,
Err(e) => return Ok(e.into_response()),
@@ -476,7 +476,7 @@ pub async fn put_record(
}
}
let existing_cid = mst.get(&key).await.ok().flatten();
let record_ipld = crate::util::json_to_ipld(&input.record);
let record_ipld = tranquil_pds::util::json_to_ipld(&input.record);
let mut record_bytes = Vec::new();
if serde_ipld_dagcbor::to_writer(&mut record_bytes, &record_ipld).is_err() {
return Ok(ApiError::InvalidRecord("Failed to serialize record".into()).into_response());
@@ -1,10 +1,10 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Auth, NotTakendown, Permissive, require_legacy_session_mfa};
use crate::cache::Cache;
use crate::plc::PlcClient;
use crate::state::AppState;
use crate::types::PlainPassword;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Auth, NotTakendown, Permissive, require_legacy_session_mfa};
use tranquil_pds::cache::Cache;
use tranquil_pds::plc::PlcClient;
use tranquil_pds::state::AppState;
use tranquil_pds::types::PlainPassword;
use axum::{
Json,
extract::State,
@@ -117,7 +117,7 @@ pub async fn check_account_status(
async fn is_valid_did_for_service(
user_repo: &dyn tranquil_db_traits::UserRepository,
cache: Arc<dyn Cache>,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> bool {
assert_valid_did_document_for_service(user_repo, cache, did, false)
.await
@@ -127,7 +127,7 @@ async fn is_valid_did_for_service(
async fn assert_valid_did_document_for_service(
user_repo: &dyn tranquil_db_traits::UserRepository,
cache: Arc<dyn Cache>,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
with_retry: bool,
) -> Result<(), ApiError> {
let hostname = &tranquil_config::get().server.hostname;
@@ -226,7 +226,7 @@ async fn assert_valid_did_document_for_service(
if let Some(key_info) = user_key {
let key_bytes =
crate::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version)
tranquil_pds::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version)
.map_err(|e| {
error!("Failed to decrypt user key: {}", e);
ApiError::InternalError(None)
@@ -235,7 +235,7 @@ async fn assert_valid_did_document_for_service(
error!("Failed to create signing key: {:?}", e);
ApiError::InternalError(None)
})?;
let expected_did_key = crate::plc::signing_key_to_did_key(&signing_key);
let expected_did_key = tranquil_pds::plc::signing_key_to_did_key(&signing_key);
if doc_signing_key != Some(&expected_did_key) {
warn!(
@@ -248,7 +248,7 @@ async fn assert_valid_did_document_for_service(
}
}
} else if let Some(host_and_path) = did.as_str().strip_prefix("did:web:") {
let client = crate::api::proxy_client::did_resolution_client();
let client = tranquil_pds::api::proxy_client::did_resolution_client();
let decoded = host_and_path.replace("%3A", ":");
let parts: Vec<&str> = decoded.split(':').collect();
let (host, path_parts) = if parts.len() > 1 && parts[1].chars().all(|c| c.is_ascii_digit())
@@ -284,7 +284,7 @@ async fn assert_valid_did_document_for_service(
arr.iter().find(|svc| {
svc.get("id").and_then(|id| id.as_str()) == Some("#atproto_pds")
|| svc.get("type").and_then(|t| t.as_str())
== Some(crate::plc::ServiceType::Pds.as_str())
== Some(tranquil_pds::plc::ServiceType::Pds.as_str())
})
})
.and_then(|svc| svc.get("serviceEndpoint"))
@@ -314,11 +314,11 @@ pub async fn activate_account(
auth.did
);
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Repo,
crate::oauth::scopes::AccountAction::Manage,
tranquil_pds::oauth::scopes::AccountAttr::Repo,
tranquil_pds::oauth::scopes::AccountAction::Manage,
) {
info!("[MIGRATION] activateAccount: Scope check failed");
return Ok(e);
@@ -365,15 +365,15 @@ pub async fn activate_account(
did
);
if let Some(ref h) = handle {
let _ = state.cache.delete(&crate::cache_keys::handle_key(h)).await;
let _ = state.cache.delete(&tranquil_pds::cache_keys::handle_key(h)).await;
}
let _ = state
.cache
.delete(&crate::cache_keys::plc_doc_key(&did))
.delete(&tranquil_pds::cache_keys::plc_doc_key(&did))
.await;
let _ = state
.cache
.delete(&crate::cache_keys::plc_data_key(&did))
.delete(&tranquil_pds::cache_keys::plc_data_key(&did))
.await;
if state.did_resolver.refresh_did(did.as_str()).await.is_none() {
warn!(
@@ -385,7 +385,7 @@ pub async fn activate_account(
"[MIGRATION] activateAccount: Sequencing account event (active=true) for did={}",
did
);
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did,
tranquil_db_traits::AccountStatus::Active,
@@ -404,7 +404,7 @@ pub async fn activate_account(
did, handle
);
let handle_typed = handle.clone();
if let Err(e) = crate::api::repo::record::sequence_identity_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(
&state,
&did,
handle_typed.as_ref(),
@@ -438,7 +438,7 @@ pub async fn activate_account(
} else {
None
};
if let Err(e) = crate::api::repo::record::sequence_sync_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_sync_event(
&state,
&did,
root_cid_link.as_str(),
@@ -483,11 +483,11 @@ pub async fn deactivate_account(
auth: Auth<Permissive>,
Json(input): Json<DeactivateAccountInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Repo,
crate::oauth::scopes::AccountAction::Manage,
tranquil_pds::oauth::scopes::AccountAttr::Repo,
tranquil_pds::oauth::scopes::AccountAction::Manage,
) {
return Ok(e);
}
@@ -507,9 +507,9 @@ pub async fn deactivate_account(
match result {
Ok(true) => {
if let Some(ref h) = handle {
let _ = state.cache.delete(&crate::cache_keys::handle_key(h)).await;
let _ = state.cache.delete(&tranquil_pds::cache_keys::handle_key(h)).await;
}
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did,
tranquil_db_traits::AccountStatus::Deactivated,
@@ -552,7 +552,7 @@ pub async fn request_account_delete(
.await
.log_db_err("creating deletion token")?;
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_account_deletion(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_account_deletion(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -569,7 +569,7 @@ pub async fn request_account_delete(
#[derive(Deserialize)]
pub struct DeleteAccountInput {
pub did: crate::types::Did,
pub did: tranquil_pds::types::Did,
pub password: PlainPassword,
pub token: String,
}
@@ -642,7 +642,7 @@ pub async fn delete_account(
error!("DB error deleting account: {:?}", e);
return ApiError::InternalError(None).into_response();
}
let account_seq = crate::api::repo::record::sequence_account_event(
let account_seq = tranquil_pds::repo_ops::sequence_account_event(
&state,
did,
tranquil_db_traits::AccountStatus::Deleted,
@@ -666,7 +666,7 @@ pub async fn delete_account(
}
let _ = state
.cache
.delete(&crate::cache_keys::handle_key(&handle))
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
.await;
info!("Account {} deleted successfully", did);
EmptyResponse::ok().into_response()
@@ -1,9 +1,9 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Auth, NotTakendown, Permissive, generate_app_password};
use crate::delegation::{DelegationActionType, intersect_scopes};
use crate::rate_limit::{AppPasswordLimit, RateLimited};
use crate::state::AppState;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Auth, NotTakendown, Permissive, generate_app_password};
use tranquil_pds::delegation::{DelegationActionType, intersect_scopes};
use tranquil_pds::rate_limit::{AppPasswordLimit, RateLimited};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -233,7 +233,7 @@ pub async fn revoke_app_password(
.log_db_err("revoking sessions for app password")?;
futures::future::join_all(sessions_to_invalidate.iter().map(|jti| {
let cache_key = crate::cache_keys::session_key(&auth.did, jti);
let cache_key = tranquil_pds::cache_keys::session_key(&auth.did, jti);
let cache = state.cache.clone();
async move {
let _ = cache.delete(&cache_key).await;
@@ -1,8 +1,8 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse};
use crate::auth::{Auth, NotTakendown};
use crate::rate_limit::{EmailUpdateLimit, RateLimited, VerificationCheckLimit};
use crate::state::AppState;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse};
use tranquil_pds::auth::{Auth, NotTakendown};
use tranquil_pds::rate_limit::{EmailUpdateLimit, RateLimited, VerificationCheckLimit};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -20,7 +20,7 @@ use tranquil_db_traits::CommsChannel;
const EMAIL_UPDATE_TTL: Duration = Duration::from_secs(30 * 60);
fn email_update_cache_key(did: &str) -> String {
crate::cache_keys::email_update_key(did)
tranquil_pds::cache_keys::email_update_key(did)
}
fn hash_token(token: &str) -> String {
@@ -49,11 +49,11 @@ pub async fn request_email_update(
auth: Auth<NotTakendown>,
input: Option<Json<RequestEmailUpdateInput>>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Email,
crate::oauth::scopes::AccountAction::Manage,
tranquil_pds::oauth::scopes::AccountAttr::Email,
tranquil_pds::oauth::scopes::AccountAction::Manage,
) {
return Ok(e);
}
@@ -74,10 +74,10 @@ pub async fn request_email_update(
let token_required = user.email_verified;
if token_required {
let token = crate::auth::email_token::create_email_token(
let token = tranquil_pds::auth::email_token::create_email_token(
state.cache.as_ref(),
auth.did.as_str(),
crate::auth::email_token::EmailTokenPurpose::UpdateEmail,
tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail,
)
.await
.map_err(|e| {
@@ -89,7 +89,7 @@ pub async fn request_email_update(
&& let Some(ref new_email) = inp.new_email
{
let new_email = new_email.trim().to_lowercase();
if !new_email.is_empty() && crate::api::validation::is_valid_email(&new_email) {
if !new_email.is_empty() && tranquil_pds::api::validation::is_valid_email(&new_email) {
let pending = PendingEmailUpdate {
new_email,
token_hash: hash_token(&token),
@@ -105,7 +105,7 @@ pub async fn request_email_update(
}
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_short_token_email(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_short_token_email(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user.id,
@@ -135,11 +135,11 @@ pub async fn confirm_email(
auth: Auth<NotTakendown>,
Json(input): Json<ConfirmEmailInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Email,
crate::oauth::scopes::AccountAction::Manage,
tranquil_pds::oauth::scopes::AccountAttr::Email,
tranquil_pds::oauth::scopes::AccountAction::Manage,
) {
return Ok(e);
}
@@ -167,9 +167,9 @@ pub async fn confirm_email(
}
let confirmation_code =
crate::auth::verification_token::normalize_token_input(input.token.trim());
tranquil_pds::auth::verification_token::normalize_token_input(input.token.trim());
let verified = crate::auth::verification_token::verify_signup_token(
let verified = tranquil_pds::auth::verification_token::verify_signup_token(
&confirmation_code,
CommsChannel::Email,
&provided_email,
@@ -181,7 +181,7 @@ pub async fn confirm_email(
return Err(ApiError::InvalidToken(None));
}
}
Err(crate::auth::verification_token::VerifyError::Expired) => {
Err(tranquil_pds::auth::verification_token::VerifyError::Expired) => {
return Err(ApiError::ExpiredToken(None));
}
Err(_) => {
@@ -213,11 +213,11 @@ pub async fn update_email(
auth: Auth<NotTakendown>,
Json(input): Json<UpdateEmailInput>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Email,
crate::oauth::scopes::AccountAction::Manage,
tranquil_pds::oauth::scopes::AccountAttr::Email,
tranquil_pds::oauth::scopes::AccountAction::Manage,
) {
return Ok(e);
}
@@ -235,7 +235,7 @@ pub async fn update_email(
let email_verified = user.email_verified;
let new_email = input.email.trim().to_lowercase();
if !crate::api::validation::is_valid_email(&new_email) {
if !tranquil_pds::api::validation::is_valid_email(&new_email) {
return Err(ApiError::InvalidRequest(
"This email address is not supported, please use a different email.".into(),
));
@@ -255,15 +255,15 @@ pub async fn update_email(
.filter(|t| !t.is_empty())
.ok_or(ApiError::TokenRequired)?;
crate::auth::email_token::validate_email_token(
tranquil_pds::auth::email_token::validate_email_token(
state.cache.as_ref(),
did.as_str(),
crate::auth::email_token::EmailTokenPurpose::UpdateEmail,
tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail,
token,
)
.await
.map_err(|e| match e {
crate::auth::email_token::TokenError::ExpiredToken => {
tranquil_pds::auth::email_token::TokenError::ExpiredToken => {
ApiError::ExpiredToken(None)
}
_ => ApiError::InvalidToken(None),
@@ -303,24 +303,24 @@ pub async fn update_email(
.filter(|t| !t.is_empty())
.ok_or(ApiError::TokenRequired)?;
let short_token_result = crate::auth::email_token::validate_email_token(
let short_token_result = tranquil_pds::auth::email_token::validate_email_token(
state.cache.as_ref(),
did.as_str(),
crate::auth::email_token::EmailTokenPurpose::UpdateEmail,
tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail,
token,
)
.await;
if let Err(e) = short_token_result {
let confirmation_token =
crate::auth::verification_token::normalize_token_input(token.trim());
tranquil_pds::auth::verification_token::normalize_token_input(token.trim());
let current_email_lower = current_email
.as_ref()
.map(|e| e.to_lowercase())
.unwrap_or_default();
let verified = crate::auth::verification_token::verify_channel_update_token(
let verified = tranquil_pds::auth::verification_token::verify_channel_update_token(
&confirmation_token,
CommsChannel::Email,
&current_email_lower,
@@ -332,9 +332,9 @@ pub async fn update_email(
return Err(ApiError::InvalidToken(None));
}
}
Err(crate::auth::verification_token::VerifyError::Expired) => {
Err(tranquil_pds::auth::verification_token::VerifyError::Expired) => {
return Err(match e {
crate::auth::email_token::TokenError::ExpiredToken => {
tranquil_pds::auth::email_token::TokenError::ExpiredToken => {
ApiError::ExpiredToken(None)
}
_ => ApiError::InvalidToken(None),
@@ -342,7 +342,7 @@ pub async fn update_email(
}
Err(_) => {
return Err(match e {
crate::auth::email_token::TokenError::ExpiredToken => {
tranquil_pds::auth::email_token::TokenError::ExpiredToken => {
ApiError::ExpiredToken(None)
}
_ => ApiError::InvalidToken(None),
@@ -359,15 +359,15 @@ pub async fn update_email(
.await
.log_db_err("updating email")?;
let verification_token = crate::auth::verification_token::generate_signup_token(
let verification_token = tranquil_pds::auth::verification_token::generate_signup_token(
did,
CommsChannel::Email,
&new_email,
);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -423,7 +423,7 @@ pub async fn check_email_verified(
#[derive(Deserialize)]
pub struct CheckChannelVerifiedInput {
pub did: crate::types::Did,
pub did: tranquil_pds::types::Did,
pub channel: CommsChannel,
}
@@ -456,11 +456,11 @@ pub async fn authorize_email_update(
_rate_limit: RateLimited<VerificationCheckLimit>,
axum::extract::Query(query): axum::extract::Query<AuthorizeEmailUpdateQuery>,
) -> Response {
let verified = crate::auth::verification_token::verify_token_signature(&query.token);
let verified = tranquil_pds::auth::verification_token::verify_token_signature(&query.token);
let token_data = match verified {
Ok(data) => data,
Err(crate::auth::verification_token::VerifyError::Expired) => {
Err(tranquil_pds::auth::verification_token::VerifyError::Expired) => {
warn!("authorize_email_update: token expired");
return ApiError::ExpiredToken(None).into_response();
}
@@ -470,7 +470,7 @@ pub async fn authorize_email_update(
}
};
if token_data.purpose != crate::auth::verification_token::VerificationPurpose::ChannelUpdate {
if token_data.purpose != tranquil_pds::auth::verification_token::VerificationPurpose::ChannelUpdate {
warn!(
"authorize_email_update: wrong purpose: {:?}",
token_data.purpose
@@ -544,11 +544,11 @@ pub async fn check_email_update_status(
_rate_limit: RateLimited<VerificationCheckLimit>,
auth: Auth<NotTakendown>,
) -> Result<Response, ApiError> {
if let Err(e) = crate::auth::scope_check::check_account_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_account_scope(
&auth.auth_source,
auth.scope.as_deref(),
crate::oauth::scopes::AccountAttr::Email,
crate::oauth::scopes::AccountAction::Read,
tranquil_pds::oauth::scopes::AccountAttr::Email,
tranquil_pds::oauth::scopes::AccountAction::Read,
) {
return Ok(e);
}
@@ -1,8 +1,8 @@
use crate::api::ApiError;
use crate::api::error::DbResultExt;
use crate::auth::{Admin, Auth, NotTakendown};
use crate::state::AppState;
use crate::types::Did;
use tranquil_pds::api::ApiError;
use tranquil_pds::api::error::DbResultExt;
use tranquil_pds::auth::{Admin, Auth, NotTakendown};
use tranquil_pds::state::AppState;
use tranquil_pds::types::Did;
use axum::{
Json,
extract::State,
@@ -1,4 +1,4 @@
use crate::state::AppState;
use tranquil_pds::state::AppState;
use axum::{
body::Body,
extract::State,
@@ -21,7 +21,7 @@ pub async fn get_logo(State(state): State<AppState>) -> Response {
Some(c) if !c.is_empty() => c,
_ => return StatusCode::NOT_FOUND.into_response(),
};
let cid = match crate::types::CidLink::new(&cid_str) {
let cid = match tranquil_pds::types::CidLink::new(&cid_str) {
Ok(c) => c,
Err(_) => return StatusCode::NOT_FOUND.into_response(),
};
@@ -1,6 +1,6 @@
use crate::BUILD_VERSION;
use crate::state::AppState;
use crate::util::{discord_app_id, discord_bot_username, telegram_bot_username};
use tranquil_pds::BUILD_VERSION;
use tranquil_pds::state::AppState;
use tranquil_pds::util::{discord_app_id, discord_bot_username, telegram_bot_username};
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
use serde_json::json;
@@ -1,7 +1,7 @@
use crate::api::ApiError;
use crate::api::error::DbResultExt;
use crate::auth::{Active, Auth};
use crate::state::AppState;
use tranquil_pds::api::ApiError;
use tranquil_pds::api::error::DbResultExt;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -145,7 +145,7 @@ pub async fn get_did_document(
Ok((StatusCode::OK, Json(json!({ "didDocument": did_doc }))).into_response())
}
async fn build_did_document(state: &AppState, did: &crate::types::Did) -> serde_json::Value {
async fn build_did_document(state: &AppState, did: &tranquil_pds::types::Did) -> serde_json::Value {
let hostname = &tranquil_config::get().server.hostname;
let user = match state.user_repo.get_user_for_did_doc_build(did).await {
@@ -195,7 +195,7 @@ async fn build_did_document(state: &AppState, did: &crate::types::Did) -> serde_
})).collect::<Vec<_>>(),
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
});
@@ -209,8 +209,8 @@ async fn build_did_document(state: &AppState, did: &crate::types::Did) -> serde_
.flatten();
let public_key_multibase = match key_info {
Some(info) => match crate::config::decrypt_key(&info.key_bytes, info.encryption_version) {
Ok(key_bytes) => crate::api::identity::did::get_public_key_multibase(&key_bytes)
Some(info) => match tranquil_pds::config::decrypt_key(&info.key_bytes, info.encryption_version) {
Ok(key_bytes) => crate::identity::did::get_public_key_multibase(&key_bytes)
.unwrap_or_else(|_| "error".to_string()),
Err(_) => "error".to_string(),
},
@@ -243,7 +243,7 @@ async fn build_did_document(state: &AppState, did: &crate::types::Did) -> serde_
}],
"service": [{
"id": "#atproto_pds",
"type": crate::plc::ServiceType::Pds.as_str(),
"type": tranquil_pds::plc::ServiceType::Pds.as_str(),
"serviceEndpoint": service_endpoint
}]
})
@@ -1,6 +1,6 @@
use crate::api::SuccessResponse;
use crate::api::error::ApiError;
use crate::auth::NormalizedLoginIdentifier;
use tranquil_pds::api::SuccessResponse;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::NormalizedLoginIdentifier;
use axum::{
Json,
extract::State,
@@ -19,12 +19,12 @@ use tracing::{debug, error, info, warn};
use tranquil_db_traits::WebauthnChallengeType;
use uuid::Uuid;
use crate::api::repo::record::utils::create_signed_commit;
use crate::auth::{ServiceTokenVerifier, generate_app_password, is_service_token};
use crate::rate_limit::{AccountCreationLimit, PasswordResetLimit, RateLimited};
use crate::state::AppState;
use crate::types::{Did, Handle, PlainPassword};
use crate::validation::validate_password;
use tranquil_pds::repo_ops::create_signed_commit;
use tranquil_pds::auth::{ServiceTokenVerifier, generate_app_password, is_service_token};
use tranquil_pds::rate_limit::{AccountCreationLimit, PasswordResetLimit, RateLimited};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle, PlainPassword};
use tranquil_pds::validation::validate_password;
fn generate_setup_token() -> String {
let mut rng = rand::thread_rng();
@@ -72,8 +72,8 @@ pub async fn create_passkey_account(
headers: HeaderMap,
Json(input): Json<CreatePasskeyAccountInput>,
) -> Response {
let byod_auth = if let Some(extracted) = crate::auth::extract_auth_token_from_header(
crate::util::get_header_str(&headers, http::header::AUTHORIZATION),
let byod_auth = if let Some(extracted) = tranquil_pds::auth::extract_auth_token_from_header(
tranquil_pds::util::get_header_str(&headers, http::header::AUTHORIZATION),
) {
let token = extracted.token;
if is_service_token(&token) {
@@ -128,14 +128,14 @@ pub async fn create_passkey_account(
.unwrap_or(&input.handle),
None => &input.handle,
};
match crate::api::validation::validate_short_handle(handle_to_validate) {
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
Ok(h) => format!("{}.{}", h, matched_domain.unwrap_or(&available_domains[0])),
Err(_) => {
return ApiError::InvalidHandle(None).into_response();
}
}
} else {
match crate::api::validation::validate_full_domain_handle(&input.handle) {
match tranquil_pds::api::validation::validate_full_domain_handle(&input.handle) {
Ok(h) => h,
Err(_) => return ApiError::InvalidHandle(None).into_response(),
}
@@ -147,7 +147,7 @@ pub async fn create_passkey_account(
.map(|e| e.trim().to_string())
.filter(|e| !e.is_empty());
if let Some(ref email) = email
&& !crate::api::validation::is_valid_email(email)
&& !tranquil_pds::api::validation::is_valid_email(email)
{
return ApiError::InvalidEmail.into_response();
}
@@ -184,7 +184,7 @@ pub async fn create_passkey_account(
tranquil_db_traits::CommsChannel::Discord => match &input.discord_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().to_lowercase();
if !crate::api::validation::is_valid_discord_username(&clean) {
if !tranquil_pds::api::validation::is_valid_discord_username(&clean) {
return ApiError::InvalidRequest(
"Invalid Discord username. Must be 2-32 lowercase characters (letters, numbers, underscores, periods)".into(),
).into_response();
@@ -196,7 +196,7 @@ pub async fn create_passkey_account(
tranquil_db_traits::CommsChannel::Telegram => match &input.telegram_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().trim_start_matches('@');
if !crate::api::validation::is_valid_telegram_username(clean) {
if !tranquil_pds::api::validation::is_valid_telegram_username(clean) {
return ApiError::InvalidRequest(
"Invalid Telegram username. Must be 5-32 characters, alphanumeric or underscore".into(),
).into_response();
@@ -250,7 +250,7 @@ pub async fn create_passkey_account(
let did = match did_type {
"web" => {
if !crate::api::server::meta::is_self_hosted_did_web_enabled() {
if !tranquil_pds::util::is_self_hosted_did_web_enabled() {
return ApiError::SelfHostedDidWebDisabled.into_response();
}
let encoded_handle = handle.replace(':', "%3A");
@@ -284,7 +284,7 @@ pub async fn create_passkey_account(
}
info!(did = %d, "Creating external did:web passkey account (BYOD key)");
} else {
if let Err(e) = crate::api::identity::did::verify_did_web(
if let Err(e) = crate::identity::did::verify_did_web(
d,
hostname,
&input.handle,
@@ -328,9 +328,9 @@ pub async fn create_passkey_account(
.secrets
.plc_rotation_key
.clone()
.unwrap_or_else(|| crate::plc::signing_key_to_did_key(&secret_key));
.unwrap_or_else(|| tranquil_pds::plc::signing_key_to_did_key(&secret_key));
let genesis_result = match crate::plc::create_genesis_operation(
let genesis_result = match tranquil_pds::plc::create_genesis_operation(
&secret_key,
&rotation_key,
&handle,
@@ -346,7 +346,7 @@ pub async fn create_passkey_account(
}
};
let plc_client = crate::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
if let Err(e) = plc_client
.send_operation(&genesis_result.did, &genesis_result.signed_operation)
.await
@@ -381,7 +381,7 @@ pub async fn create_passkey_account(
None
};
let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) {
let encrypted_key_bytes = match tranquil_pds::config::encrypt_key(&secret_key_bytes) {
Ok(bytes) => bytes,
Err(e) => {
error!("Error encrypting signing key: {:?}", e);
@@ -458,7 +458,7 @@ pub async fn create_passkey_account(
setup_expires_at,
deactivated_at,
encrypted_key_bytes,
encryption_version: crate::config::ENCRYPTION_VERSION,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
reserved_key_id,
commit_cid: commit_cid.to_string(),
repo_rev: rev.as_ref().to_string(),
@@ -487,7 +487,7 @@ pub async fn create_passkey_account(
let user_id = create_result.user_id;
if !is_byod_did_web {
if let Err(e) = crate::api::repo::record::sequence_identity_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(
&state,
&did_typed,
Some(&handle_typed),
@@ -496,7 +496,7 @@ pub async fn create_passkey_account(
{
warn!("Failed to sequence identity event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did_typed,
tranquil_db_traits::AccountStatus::Active,
@@ -509,11 +509,11 @@ pub async fn create_passkey_account(
"$type": "app.bsky.actor.profile",
"displayName": handle
});
if let Err(e) = crate::api::repo::record::create_record_internal(
if let Err(e) = tranquil_pds::repo_ops::create_record_internal(
&state,
&did_typed,
&crate::types::PROFILE_COLLECTION,
&crate::types::PROFILE_RKEY,
&tranquil_pds::types::PROFILE_COLLECTION,
&tranquil_pds::types::PROFILE_RKEY,
&profile_record,
)
.await
@@ -522,14 +522,14 @@ pub async fn create_passkey_account(
}
}
let verification_token = crate::auth::verification_token::generate_signup_token(
let verification_token = tranquil_pds::auth::verification_token::generate_signup_token(
&did_typed,
verification_channel,
&verification_recipient,
);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -546,7 +546,7 @@ pub async fn create_passkey_account(
info!(did = %did, handle = %handle, "Passkey-only account created, awaiting setup completion");
let access_jwt = if byod_auth.is_some() {
match crate::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
Ok(token_meta) => {
let refresh_jti = uuid::Uuid::new_v4().to_string();
let refresh_expires = chrono::Utc::now() + chrono::Duration::hours(24);
@@ -887,7 +887,7 @@ pub async fn request_passkey_recovery(
urlencoding::encode(&recovery_token)
);
let _ = crate::comms::comms_repo::enqueue_passkey_recovery(
let _ = tranquil_pds::comms::comms_repo::enqueue_passkey_recovery(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user.id,
@@ -968,7 +968,7 @@ pub async fn recover_passkey_account(
}
if let Ok(Some(prefs)) = state.user_repo.get_comms_prefs(user.id).await {
let actual_channel =
crate::comms::resolve_delivery_channel(&prefs, user.preferred_comms_channel);
tranquil_pds::comms::resolve_delivery_channel(&prefs, user.preferred_comms_channel);
if let Err(e) = state
.user_repo
.set_channel_verified(&input.did, actual_channel)
@@ -1,7 +1,7 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{Active, Auth, require_legacy_session_mfa, require_reauth_window};
use crate::state::AppState;
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Active, Auth, require_legacy_session_mfa, require_reauth_window};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -271,6 +271,6 @@ pub async fn update_passkey(
}
}
pub async fn has_passkeys_for_user(state: &AppState, did: &crate::types::Did) -> bool {
pub async fn has_passkeys_for_user(state: &AppState, did: &tranquil_pds::types::Did) -> bool {
state.user_repo.has_passkeys(did).await.unwrap_or(false)
}
@@ -1,13 +1,13 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::api::{EmptyResponse, HasPasswordResponse, SuccessResponse};
use crate::auth::{
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::api::{EmptyResponse, HasPasswordResponse, SuccessResponse};
use tranquil_pds::auth::{
Active, Auth, NormalizedLoginIdentifier, require_legacy_session_mfa, require_reauth_window,
require_reauth_window_if_available,
};
use crate::rate_limit::{PasswordResetLimit, RateLimited, ResetPasswordLimit};
use crate::state::AppState;
use crate::types::PlainPassword;
use crate::validation::validate_password;
use tranquil_pds::rate_limit::{PasswordResetLimit, RateLimited, ResetPasswordLimit};
use tranquil_pds::state::AppState;
use tranquil_pds::types::PlainPassword;
use tranquil_pds::validation::validate_password;
use axum::{
Json,
extract::State,
@@ -19,7 +19,7 @@ use serde::Deserialize;
use tracing::{error, info, warn};
fn generate_reset_code() -> String {
crate::util::generate_token_code()
tranquil_pds::util::generate_token_code()
}
#[derive(Deserialize)]
@@ -78,7 +78,7 @@ pub async fn request_password_reset(
return ApiError::InternalError(None).into_response();
}
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_password_reset(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_password_reset(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id,
@@ -170,7 +170,7 @@ pub async fn reset_password(
}
};
futures::future::join_all(result.session_jtis.iter().map(|jti| {
let cache_key = crate::cache_keys::session_key(&result.did, jti);
let cache_key = tranquil_pds::cache_keys::session_key(&result.did, jti);
let cache = state.cache.clone();
async move {
if let Err(e) = cache.delete(&cache_key).await {
@@ -184,7 +184,7 @@ pub async fn reset_password(
.await;
if let Ok(Some(prefs)) = state.user_repo.get_comms_prefs(user_id).await {
let actual_channel =
crate::comms::resolve_delivery_channel(&prefs, user.preferred_comms_channel);
tranquil_pds::comms::resolve_delivery_channel(&prefs, user.preferred_comms_channel);
if let Err(e) = state
.user_repo
.set_channel_verified(&user.did, actual_channel)
@@ -212,7 +212,7 @@ pub async fn change_password(
auth: Auth<Active>,
Json(input): Json<ChangePasswordInput>,
) -> Result<Response, ApiError> {
use crate::auth::verify_password_mfa;
use tranquil_pds::auth::verify_password_mfa;
let session_mfa = match require_legacy_session_mfa(&state, &auth).await {
Ok(proof) => proof,
@@ -1,4 +1,4 @@
use crate::api::error::{ApiError, DbResultExt};
use tranquil_pds::api::error::{ApiError, DbResultExt};
use axum::{
Json,
extract::State,
@@ -10,10 +10,10 @@ use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
use tranquil_db_traits::{SessionRepository, UserRepository, WebauthnChallengeType};
use crate::auth::{Active, Auth};
use crate::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message};
use crate::state::AppState;
use crate::types::PlainPassword;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message};
use tranquil_pds::state::AppState;
use tranquil_pds::types::PlainPassword;
pub const REAUTH_WINDOW_SECONDS: i64 = 300;
@@ -125,7 +125,7 @@ pub async fn reauth_totp(
.await?;
let valid =
crate::api::server::totp::verify_totp_or_backup_for_user(&state, &auth.did, &input.code)
crate::server::totp::verify_totp_or_backup_for_user(&state, &auth.did, &input.code)
.await;
if !valid {
@@ -276,11 +276,11 @@ pub async fn reauth_passkey_finish(
pub async fn update_last_reauth_cached(
session_repo: &dyn SessionRepository,
cache: &std::sync::Arc<dyn crate::cache::Cache>,
did: &crate::types::Did,
cache: &std::sync::Arc<dyn tranquil_pds::cache::Cache>,
did: &tranquil_pds::types::Did,
) -> Result<DateTime<Utc>, tranquil_db_traits::DbError> {
let now = session_repo.update_last_reauth(did).await?;
let cache_key = crate::cache_keys::reauth_key(did);
let cache_key = tranquil_pds::cache_keys::reauth_key(did);
let _ = cache
.set(
&cache_key,
@@ -304,7 +304,7 @@ fn is_reauth_required(last_reauth_at: Option<DateTime<Utc>>) -> bool {
async fn get_available_reauth_methods(
user_repo: &dyn UserRepository,
_session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> Vec<ReauthMethod> {
let mut methods = Vec::new();
@@ -334,7 +334,7 @@ async fn get_available_reauth_methods(
pub async fn check_reauth_required(
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> bool {
match session_repo.get_last_reauth_at(did).await {
Ok(last_reauth_at) => is_reauth_required(last_reauth_at),
@@ -344,10 +344,10 @@ pub async fn check_reauth_required(
pub async fn check_reauth_required_cached(
session_repo: &dyn SessionRepository,
cache: &std::sync::Arc<dyn crate::cache::Cache>,
did: &crate::types::Did,
cache: &std::sync::Arc<dyn tranquil_pds::cache::Cache>,
did: &tranquil_pds::types::Did,
) -> bool {
let cache_key = crate::cache_keys::reauth_key(did);
let cache_key = tranquil_pds::cache_keys::reauth_key(did);
if let Some(timestamp_str) = cache.get(&cache_key).await
&& let Ok(timestamp) = timestamp_str.parse::<i64>()
{
@@ -376,7 +376,7 @@ pub struct ReauthRequiredError {
pub async fn reauth_required_response(
user_repo: &dyn UserRepository,
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> Response {
let methods = get_available_reauth_methods(user_repo, session_repo, did).await;
(
@@ -392,7 +392,7 @@ pub async fn reauth_required_response(
pub async fn check_legacy_session_mfa(
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> bool {
match session_repo.get_session_mfa_status(did).await {
Ok(Some(status)) => {
@@ -416,7 +416,7 @@ pub async fn check_legacy_session_mfa(
pub async fn update_mfa_verified(
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> Result<(), tranquil_db_traits::DbError> {
session_repo.update_mfa_verified(did).await
}
@@ -424,7 +424,7 @@ pub async fn update_mfa_verified(
pub async fn legacy_mfa_required_response(
user_repo: &dyn UserRepository,
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
) -> Response {
let methods = get_available_reauth_methods(user_repo, session_repo, did).await;
(
@@ -1,7 +1,7 @@
use crate::api::error::ApiError;
use crate::auth::extractor::{Auth, Permissive};
use crate::state::AppState;
use crate::types::Did;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::extractor::{Auth, Permissive};
use tranquil_pds::state::AppState;
use tranquil_pds::types::Did;
use axum::{
Json,
extract::{Query, State},
@@ -75,7 +75,7 @@ pub async fn get_service_auth(
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) {
match tranquil_pds::config::decrypt_key(&key_bytes_enc, info.encryption_version) {
Ok(key) => key,
Err(e) => {
error!(error = ?e, "Failed to decrypt user key for service auth");
@@ -112,7 +112,7 @@ pub async fn get_service_auth(
let lxm_for_token = lxm.map_or("*", |n| n.as_str());
if let Some(method) = lxm {
if let Err(e) = crate::auth::scope_check::check_rpc_scope(
if let Err(e) = tranquil_pds::auth::scope_check::check_rpc_scope(
&auth.auth_source,
auth.scope.as_deref(),
params.aud.as_str(),
@@ -167,7 +167,7 @@ pub async fn get_service_auth(
}
}
let service_token = match crate::auth::create_service_token(
let service_token = match tranquil_pds::auth::create_service_token(
&auth.did,
params.aud.as_str(),
lxm_for_token,
@@ -1,12 +1,12 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::api::{EmptyResponse, SuccessResponse};
use crate::auth::{
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::api::{EmptyResponse, SuccessResponse};
use tranquil_pds::auth::{
Active, Auth, NormalizedLoginIdentifier, Permissive, require_legacy_session_mfa,
require_reauth_window,
};
use crate::rate_limit::{LoginLimit, RateLimited, RefreshSessionLimit};
use crate::state::AppState;
use crate::types::{AccountState, Did, Handle, PlainPassword};
use tranquil_pds::rate_limit::{LoginLimit, RateLimited, RefreshSessionLimit};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{AccountState, Did, Handle, PlainPassword};
use axum::{
Json,
extract::State,
@@ -93,7 +93,7 @@ pub async fn create_session(
return ApiError::InternalError(None).into_response();
}
};
let key_bytes = match crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) {
Ok(k) => k,
Err(e) => {
error!("Failed to decrypt user key: {:?}", e);
@@ -173,12 +173,12 @@ pub async fn create_session(
let has_totp = row.totp_enabled;
let email_2fa_enabled = row.email_2fa_enabled;
let is_legacy_login = has_totp || email_2fa_enabled;
let twofa_ctx = crate::auth::legacy_2fa::Legacy2faContext {
let twofa_ctx = tranquil_pds::auth::legacy_2fa::Legacy2faContext {
email_2fa_enabled,
has_totp,
allow_legacy_login: row.allow_legacy_login,
};
match crate::auth::legacy_2fa::process_legacy_2fa(
match tranquil_pds::auth::legacy_2fa::process_legacy_2fa(
state.cache.as_ref(),
&row.did,
&twofa_ctx,
@@ -186,14 +186,14 @@ pub async fn create_session(
)
.await
{
Ok(crate::auth::legacy_2fa::Legacy2faOutcome::NotRequired) => {}
Ok(crate::auth::legacy_2fa::Legacy2faOutcome::Blocked) => {
Ok(tranquil_pds::auth::legacy_2fa::Legacy2faOutcome::NotRequired) => {}
Ok(tranquil_pds::auth::legacy_2fa::Legacy2faOutcome::Blocked) => {
warn!("Legacy login blocked for TOTP-enabled account: {}", row.did);
return ApiError::LegacyLoginBlocked.into_response();
}
Ok(crate::auth::legacy_2fa::Legacy2faOutcome::ChallengeSent(code)) => {
Ok(tranquil_pds::auth::legacy_2fa::Legacy2faOutcome::ChallengeSent(code)) => {
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_2fa_code(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_2fa_code(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
row.id,
@@ -203,7 +203,7 @@ pub async fn create_session(
.await
{
error!("Failed to send 2FA code: {:?}", e);
crate::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &row.did).await;
tranquil_pds::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &row.did).await;
return ApiError::InternalError(Some(
"Failed to send verification code. Please try again.".into(),
))
@@ -211,9 +211,9 @@ pub async fn create_session(
}
return ApiError::AuthFactorTokenRequired.into_response();
}
Ok(crate::auth::legacy_2fa::Legacy2faOutcome::Verified) => {}
Err(crate::auth::legacy_2fa::Legacy2faFlowError::Challenge(e)) => {
use crate::auth::legacy_2fa::ChallengeError;
Ok(tranquil_pds::auth::legacy_2fa::Legacy2faOutcome::Verified) => {}
Err(tranquil_pds::auth::legacy_2fa::Legacy2faFlowError::Challenge(e)) => {
use tranquil_pds::auth::legacy_2fa::ChallengeError;
return match e {
ChallengeError::CacheUnavailable => {
error!("Cache unavailable for 2FA, blocking legacy login");
@@ -232,8 +232,8 @@ pub async fn create_session(
}
};
}
Err(crate::auth::legacy_2fa::Legacy2faFlowError::Validation(e)) => {
use crate::auth::legacy_2fa::ValidationError;
Err(tranquil_pds::auth::legacy_2fa::Legacy2faFlowError::Validation(e)) => {
use tranquil_pds::auth::legacy_2fa::ValidationError;
warn!("Invalid 2FA code for {}: {:?}", row.did, e);
let msg = match e {
ValidationError::TooManyAttempts => "Too many attempts. Please request a new code.",
@@ -248,7 +248,7 @@ pub async fn create_session(
return ApiError::InvalidCode(Some(msg.into())).into_response();
}
}
let access_meta = match crate::auth::create_access_token_with_delegation(
let access_meta = match tranquil_pds::auth::create_access_token_with_delegation(
&row.did,
&key_bytes,
app_password_scopes.as_deref(),
@@ -261,7 +261,7 @@ pub async fn create_session(
return ApiError::InternalError(None).into_response();
}
};
let refresh_meta = match crate::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) {
let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) {
Ok(m) => m,
Err(e) => {
error!("Failed to create refresh token: {:?}", e);
@@ -297,7 +297,7 @@ pub async fn create_session(
"Legacy login on TOTP-enabled account - sending notification"
);
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_legacy_login(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_legacy_login(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
row.id,
@@ -406,18 +406,18 @@ pub async fn delete_session(
headers: axum::http::HeaderMap,
_auth: Auth<Active>,
) -> Result<Response, ApiError> {
let extracted = crate::auth::extract_auth_token_from_header(crate::util::get_header_str(
let extracted = tranquil_pds::auth::extract_auth_token_from_header(tranquil_pds::util::get_header_str(
&headers,
http::header::AUTHORIZATION,
))
.ok_or(ApiError::AuthenticationRequired)?;
let jti = crate::auth::get_jti_from_token(&extracted.token)
let jti = tranquil_pds::auth::get_jti_from_token(&extracted.token)
.map_err(|_| ApiError::AuthenticationFailed(None))?;
let did = crate::auth::get_did_from_token(&extracted.token).ok();
let did = tranquil_pds::auth::get_did_from_token(&extracted.token).ok();
match state.session_repo.delete_session_by_access_jti(&jti).await {
Ok(rows) if rows > 0 => {
if let Some(did) = did {
let session_cache_key = crate::cache_keys::session_key(&did, &jti);
let session_cache_key = tranquil_pds::cache_keys::session_key(&did, &jti);
let _ = state.cache.delete(&session_cache_key).await;
}
Ok(EmptyResponse::ok().into_response())
@@ -432,7 +432,7 @@ pub async fn refresh_session(
_rate_limit: RateLimited<RefreshSessionLimit>,
headers: axum::http::HeaderMap,
) -> Response {
let extracted = match crate::auth::extract_auth_token_from_header(crate::util::get_header_str(
let extracted = match tranquil_pds::auth::extract_auth_token_from_header(tranquil_pds::util::get_header_str(
&headers,
http::header::AUTHORIZATION,
)) {
@@ -440,7 +440,7 @@ pub async fn refresh_session(
None => return ApiError::AuthenticationRequired.into_response(),
};
let refresh_token = extracted.token;
let refresh_jti = match crate::auth::get_jti_from_token(&refresh_token) {
let refresh_jti = match tranquil_pds::auth::get_jti_from_token(&refresh_token) {
Ok(jti) => jti,
Err(_) => {
return ApiError::AuthenticationFailed(Some("Invalid token format".into()))
@@ -473,7 +473,7 @@ pub async fn refresh_session(
return ApiError::InternalError(None).into_response();
}
};
let key_bytes = match crate::config::decrypt_key(
let key_bytes = match tranquil_pds::config::decrypt_key(
&session_row.key_bytes,
Some(session_row.encryption_version),
) {
@@ -483,11 +483,11 @@ pub async fn refresh_session(
return ApiError::InternalError(None).into_response();
}
};
if crate::auth::verify_refresh_token(&refresh_token, &key_bytes).is_err() {
if tranquil_pds::auth::verify_refresh_token(&refresh_token, &key_bytes).is_err() {
return ApiError::AuthenticationFailed(Some("Invalid refresh token".into()))
.into_response();
}
let new_access_meta = match crate::auth::create_access_token_with_delegation(
let new_access_meta = match tranquil_pds::auth::create_access_token_with_delegation(
&session_row.did,
&key_bytes,
session_row.scope.as_deref(),
@@ -501,7 +501,7 @@ pub async fn refresh_session(
}
};
let new_refresh_meta =
match crate::auth::create_refresh_token_with_metadata(&session_row.did, &key_bytes) {
match tranquil_pds::auth::create_refresh_token_with_metadata(&session_row.did, &key_bytes) {
Ok(m) => m,
Err(e) => {
error!("Failed to create refresh token: {:?}", e);
@@ -641,8 +641,8 @@ pub async fn confirm_signup(
};
let normalized_token =
crate::auth::verification_token::normalize_token_input(&input.verification_code);
match crate::auth::verification_token::verify_signup_token(
tranquil_pds::auth::verification_token::normalize_token_input(&input.verification_code);
match tranquil_pds::auth::verification_token::verify_signup_token(
&normalized_token,
row.channel,
&identifier,
@@ -657,7 +657,7 @@ pub async fn confirm_signup(
.into_response();
}
}
Err(crate::auth::verification_token::VerifyError::Expired) => {
Err(tranquil_pds::auth::verification_token::VerifyError::Expired) => {
warn!("Verification code expired for user: {}", input.did);
return ApiError::ExpiredToken(Some("Verification code has expired".into()))
.into_response();
@@ -668,7 +668,7 @@ pub async fn confirm_signup(
}
}
let key_bytes = match crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) {
Ok(k) => k,
Err(e) => {
error!("Failed to decrypt user key: {:?}", e);
@@ -676,14 +676,14 @@ pub async fn confirm_signup(
}
};
let access_meta = match crate::auth::create_access_token_with_metadata(&row.did, &key_bytes) {
let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&row.did, &key_bytes) {
Ok(m) => m,
Err(e) => {
error!("Failed to create access token: {:?}", e);
return ApiError::InternalError(None).into_response();
}
};
let refresh_meta = match crate::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) {
let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) {
Ok(m) => m,
Err(e) => {
error!("Failed to create refresh token: {:?}", e);
@@ -718,7 +718,7 @@ pub async fn confirm_signup(
}
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_welcome(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_welcome(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
row.id,
@@ -749,7 +749,7 @@ pub struct AutoResendResult {
}
pub async fn auto_resend_verification(state: &AppState, did: &Did) -> Option<AutoResendResult> {
let debounce_key = crate::cache_keys::auto_verify_sent_key(did.as_str());
let debounce_key = tranquil_pds::cache_keys::auto_verify_sent_key(did.as_str());
let debounced = state.cache.get(&debounce_key).await.is_some();
let row = match state.user_repo.get_resend_verification_by_did(did).await {
Ok(Some(row)) => row,
@@ -789,11 +789,11 @@ pub async fn auto_resend_verification(state: &AppState, did: &Did) -> Option<Aut
return Some(result);
}
let verification_token =
crate::auth::verification_token::generate_signup_token(did, row.channel, &recipient);
tranquil_pds::auth::verification_token::generate_signup_token(did, row.channel, &recipient);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
row.id,
@@ -856,12 +856,12 @@ pub async fn resend_verification(
};
let verification_token =
crate::auth::verification_token::generate_signup_token(&input.did, row.channel, &recipient);
tranquil_pds::auth::verification_token::generate_signup_token(&input.did, row.channel, &recipient);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
row.id,
@@ -910,7 +910,7 @@ pub async fn list_sessions(
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.and_then(|token| crate::auth::get_jti_from_token(token).ok());
.and_then(|token| tranquil_pds::auth::get_jti_from_token(token).ok());
let jwt_rows = state
.session_repo
@@ -990,7 +990,7 @@ pub async fn revoke_session(
.delete_session_by_id(session_id)
.await
.log_db_err("deleting session")?;
let cache_key = crate::cache_keys::session_key(&auth.did, &access_jti);
let cache_key = tranquil_pds::cache_keys::session_key(&auth.did, &access_jti);
if let Err(e) = state.cache.delete(&cache_key).await {
warn!("Failed to invalidate session cache: {:?}", e);
}
@@ -1020,10 +1020,10 @@ pub async fn revoke_all_sessions(
headers: HeaderMap,
auth: Auth<Active>,
) -> Result<Response, ApiError> {
let jti = crate::auth::extract_auth_token_from_header(
let jti = tranquil_pds::auth::extract_auth_token_from_header(
headers.get("authorization").and_then(|v| v.to_str().ok()),
)
.and_then(|extracted| crate::auth::get_jti_from_token(&extracted.token).ok())
.and_then(|extracted| tranquil_pds::auth::get_jti_from_token(&extracted.token).ok())
.ok_or(ApiError::InvalidToken(None))?;
if auth.is_oauth() {
@@ -1119,7 +1119,7 @@ pub async fn update_legacy_login_preference(
.into_response())
}
use crate::comms::VALID_LOCALES;
use tranquil_pds::comms::VALID_LOCALES;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -1,5 +1,5 @@
use crate::api::error::ApiError;
use crate::state::AppState;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -25,7 +25,7 @@ fn public_key_to_did_key(signing_key: &SigningKey) -> String {
#[derive(Deserialize)]
pub struct ReserveSigningKeyInput {
pub did: Option<crate::types::Did>,
pub did: Option<tranquil_pds::types::Did>,
}
#[derive(Serialize)]
@@ -1,14 +1,14 @@
use crate::api::EmptyResponse;
use crate::api::error::{ApiError, DbResultExt};
use crate::auth::{
use tranquil_pds::api::EmptyResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{
Active, Auth, decrypt_totp_secret, encrypt_totp_secret, generate_backup_codes,
generate_qr_png_base64, generate_totp_secret, generate_totp_uri, hash_backup_code,
is_backup_code_format, require_legacy_session_mfa, verify_backup_code, verify_password_mfa,
verify_totp_code, verify_totp_mfa,
};
use crate::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message};
use crate::state::AppState;
use crate::types::PlainPassword;
use tranquil_pds::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message};
use tranquil_pds::state::AppState;
use tranquil_pds::types::PlainPassword;
use axum::{
Json,
extract::State,
@@ -186,7 +186,7 @@ pub async fn disable_totp(
.await
.log_db_err("deleting TOTP")?;
crate::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &auth.did).await;
tranquil_pds::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &auth.did).await;
info!(did = %session_mfa.did(), "TOTP disabled (verified via {} and {})", password_mfa.method(), totp_mfa.method());
@@ -280,7 +280,7 @@ pub async fn regenerate_backup_codes(
async fn verify_backup_code_for_user(
state: &AppState,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
code: &str,
) -> bool {
let code = code.trim().to_uppercase();
@@ -308,7 +308,7 @@ async fn verify_backup_code_for_user(
pub async fn verify_totp_or_backup_for_user(
state: &AppState,
did: &crate::types::Did,
did: &tranquil_pds::types::Did,
code: &str,
) -> bool {
use tranquil_db_traits::TotpRecordState;
@@ -340,6 +340,6 @@ pub async fn verify_totp_or_backup_for_user(
false
}
pub async fn has_totp_enabled(state: &AppState, did: &crate::types::Did) -> bool {
pub async fn has_totp_enabled(state: &AppState, did: &tranquil_pds::types::Did) -> bool {
state.user_repo.has_totp_enabled(did).await.unwrap_or(false)
}
@@ -1,5 +1,5 @@
use crate::api::SuccessResponse;
use crate::api::error::{ApiError, DbResultExt};
use tranquil_pds::api::SuccessResponse;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use axum::{
Json,
extract::State,
@@ -11,8 +11,8 @@ use tracing::{error, info};
use tranquil_db_traits::OAuthRepository;
use tranquil_types::DeviceId;
use crate::auth::{Active, Auth};
use crate::state::AppState;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::state::AppState;
const TRUST_DURATION_DAYS: i64 = 30;
@@ -1,10 +1,10 @@
use crate::api::error::ApiError;
use crate::types::Did;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::types::Did;
use axum::{Json, extract::State};
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use crate::state::AppState;
use tranquil_pds::state::AppState;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -71,10 +71,10 @@ pub async fn resend_migration_verification(
}
let hostname = &tranquil_config::get().server.hostname;
let token = crate::auth::verification_token::generate_migration_token(&user.did, &email);
let formatted_token = crate::auth::verification_token::format_token_for_display(&token);
let token = tranquil_pds::auth::verification_token::generate_migration_token(&user.did, &email);
let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token);
if let Err(e) = crate::comms::comms_repo::enqueue_migration_verification(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_migration_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user.id,
@@ -1,14 +1,14 @@
use crate::api::error::{ApiError, DbResultExt};
use crate::comms::comms_repo;
use crate::types::Did;
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::comms::comms_repo;
use tranquil_pds::types::Did;
use axum::{Json, extract::State};
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use crate::auth::verification_token::{
use tranquil_pds::auth::verification_token::{
VerificationPurpose, normalize_token_input, verify_token_signature,
};
use crate::state::AppState;
use tranquil_pds::state::AppState;
use tranquil_db_traits::CommsChannel;
#[derive(Deserialize, Clone)]
@@ -46,7 +46,7 @@ pub async fn verify_token_internal(
ApiError::from(e)
})?;
let expected_hash = crate::auth::verification_token::hash_identifier(&identifier);
let expected_hash = tranquil_pds::auth::verification_token::hash_identifier(&identifier);
if token_data.identifier_hash != expected_hash {
return Err(ApiError::IdentifierMismatch);
}
@@ -6,8 +6,8 @@ use axum::{
use serde::Deserialize;
use tracing::{debug, info, warn};
use crate::comms::comms_repo;
use crate::state::AppState;
use tranquil_pds::comms::comms_repo;
use tranquil_pds::state::AppState;
#[derive(Deserialize)]
struct TelegramUpdate {
@@ -1,6 +1,6 @@
use crate::api::error::ApiError;
use crate::auth::{Active, Auth, Permissive};
use crate::state::AppState;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::{Active, Auth, Permissive};
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -57,7 +57,7 @@ pub async fn dereference_scope(
for part in scope_parts {
if let Some(cid_str) = part.strip_prefix("ref:") {
let cache_key = crate::cache_keys::scope_ref_key(cid_str);
let cache_key = tranquil_pds::cache_keys::scope_ref_key(cid_str);
if let Some(cached) = state.cache.get(&cache_key).await {
for s in cached.split_whitespace() {
if !resolved_scopes.contains(&s.to_string()) {
@@ -1,5 +1,5 @@
use crate::api::SuccessResponse;
use crate::state::AppState;
use tranquil_pds::api::SuccessResponse;
use tranquil_pds::state::AppState;
use axum::{
Json,
extract::State,
@@ -19,12 +19,12 @@ pub async fn confirm_channel_verification(
State(state): State<AppState>,
Json(input): Json<ConfirmChannelVerificationInput>,
) -> Response {
let token_input = crate::api::server::VerifyTokenInput {
let token_input = crate::server::VerifyTokenInput {
token: input.code,
identifier: input.identifier,
};
match crate::api::server::verify_token_internal(&state, token_input).await {
match crate::server::verify_token_internal(&state, token_input).await {
Ok(_output) => SuccessResponse::ok().into_response(),
Err(e) => e.into_response(),
}
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "tranquil-oauth-server"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
tranquil-pds = { workspace = true }
tranquil-api = { workspace = true }
tranquil-types = { workspace = true }
tranquil-config = { workspace = true }
tranquil-crypto = { workspace = true }
tranquil-db-traits = { workspace = true }
axum = { workspace = true }
base64 = { workspace = true }
bcrypt = { workspace = true }
chrono = { workspace = true }
cid = { workspace = true }
hmac = { workspace = true }
http = { workspace = true }
jacquard-common = { workspace = true }
jacquard-repo = { workspace = true }
k256 = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_urlencoded = { workspace = true }
sha2 = { workspace = true }
subtle = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
urlencoding = { workspace = true }
uuid = { workspace = true }
webauthn-rs = { workspace = true }
@@ -1,16 +1,16 @@
use crate::auth::{BareLoginIdentifier, NormalizedLoginIdentifier};
use crate::comms::comms_repo::enqueue_2fa_code;
use crate::oauth::{
use tranquil_pds::auth::{BareLoginIdentifier, NormalizedLoginIdentifier};
use tranquil_pds::comms::comms_repo::enqueue_2fa_code;
use tranquil_pds::oauth::{
AuthFlow, ClientMetadataCache, Code, DeviceData, DeviceId, OAuthError, Prompt, SessionId,
db::should_show_consent, scopes::expand_include_scopes,
};
use crate::rate_limit::{
use tranquil_pds::rate_limit::{
OAuthAuthorizeLimit, OAuthRateLimited, OAuthRegisterCompleteLimit, TotpVerifyLimit,
check_user_rate_limit,
};
use crate::state::AppState;
use crate::types::{Did, Handle, PlainPassword};
use crate::util::extract_client_ip;
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Handle, PlainPassword};
use tranquil_pds::util::extract_client_ip;
use axum::{
Json,
extract::{Query, State},
@@ -95,7 +95,7 @@ fn extract_device_cookie(headers: &HeaderMap) -> Option<tranquil_types::DeviceId
cookie_str.split(';').map(|c| c.trim()).find_map(|cookie| {
cookie
.strip_prefix(&format!("{}=", DEVICE_COOKIE_NAME))
.and_then(|value| crate::config::AuthConfig::get().verify_device_cookie(value))
.and_then(|value| tranquil_pds::config::AuthConfig::get().verify_device_cookie(value))
.map(tranquil_types::DeviceId::new)
})
})
@@ -109,7 +109,7 @@ fn extract_user_agent(headers: &HeaderMap) -> Option<String> {
}
fn make_device_cookie(device_id: &tranquil_types::DeviceId) -> String {
let signed_value = crate::config::AuthConfig::get().sign_device_cookie(device_id.as_str());
let signed_value = tranquil_pds::config::AuthConfig::get().sign_device_cookie(device_id.as_str());
format!(
"{}={}; Path=/oauth; HttpOnly; Secure; SameSite=Lax; Max-Age=31536000",
DEVICE_COOKIE_NAME, signed_value
@@ -343,7 +343,7 @@ pub async fn authorize_get_json(
.oauth_repo
.get_authorization_request(&request_id_json)
.await
.map_err(crate::oauth::db_err_to_oauth)?
.map_err(tranquil_pds::oauth::db_err_to_oauth)?
.ok_or_else(|| OAuthError::InvalidRequest("Invalid or expired request_uri".to_string()))?;
if request_data.expires_at < Utc::now() {
let _ = state
@@ -626,7 +626,7 @@ pub async fn authorize_post(
}
let is_verified = user.channel_verification.has_any_verified();
if !is_verified {
let resend_info = crate::api::server::auto_resend_verification(&state, &user.did).await;
let resend_info = tranquil_api::server::auto_resend_verification(&state, &user.did).await;
let handle = resend_info
.as_ref()
.map(|r| r.handle.to_string())
@@ -653,11 +653,11 @@ pub async fn authorize_post(
url_encode("account_not_verified")
));
}
let has_totp = crate::api::server::has_totp_enabled(&state, &user.did).await;
let has_totp = tranquil_api::server::has_totp_enabled(&state, &user.did).await;
if has_totp {
let device_cookie = extract_device_cookie(&headers);
let device_is_trusted = if let Some(ref dev_id) = device_cookie {
crate::api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &user.did)
tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &user.did)
.await
} else {
false
@@ -665,7 +665,7 @@ pub async fn authorize_post(
if device_is_trusted {
if let Some(ref dev_id) = device_cookie {
let _ = crate::api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id)
let _ = tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id)
.await;
}
} else {
@@ -978,7 +978,7 @@ pub async fn authorize_select(
};
let is_verified = user.channel_verification.has_any_verified();
if !is_verified {
let resend_info = crate::api::server::auto_resend_verification(&state, &did).await;
let resend_info = tranquil_api::server::auto_resend_verification(&state, &did).await;
return (
StatusCode::FORBIDDEN,
Json(serde_json::json!({
@@ -991,11 +991,11 @@ pub async fn authorize_select(
)
.into_response();
}
let has_totp = crate::api::server::has_totp_enabled(&state, &did).await;
let has_totp = tranquil_api::server::has_totp_enabled(&state, &did).await;
let select_early_device_typed = device_id.clone();
if has_totp {
let device_is_trusted =
crate::api::server::is_device_trusted(state.oauth_repo.as_ref(), &device_id, &did)
tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), &device_id, &did)
.await;
if !device_is_trusted {
if state
@@ -1016,7 +1016,7 @@ pub async fn authorize_select(
.into_response();
}
let _ =
crate::api::server::extend_device_trust(state.oauth_repo.as_ref(), &device_id).await;
tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), &device_id).await;
}
if user.two_factor_enabled {
let _ = state
@@ -1413,7 +1413,7 @@ pub async fn consent_get(
};
let effective_scope_str = if let Some(ref grant) = delegation_grant {
crate::delegation::intersect_scopes(requested_scope_str, grant.granted_scopes.as_str())
tranquil_pds::delegation::intersect_scopes(requested_scope_str, grant.granted_scopes.as_str())
} else {
requested_scope_str.to_string()
};
@@ -1445,7 +1445,7 @@ pub async fn consent_get(
.iter()
.map(|scope| {
let (category, required, description, display_name) = if let Some(def) =
crate::oauth::scopes::SCOPE_DEFINITIONS.get(*scope)
tranquil_pds::oauth::scopes::SCOPE_DEFINITIONS.get(*scope)
{
let desc = if *scope == "atproto" && has_granular_scopes {
"AT Protocol baseline scope (permissions determined by selected options below)"
@@ -1510,7 +1510,7 @@ pub async fn consent_get(
.map(|h| h.to_string());
let level = if let Some(ref grant) = delegation_grant {
let preset = crate::delegation::SCOPE_PRESETS
let preset = tranquil_pds::delegation::SCOPE_PRESETS
.iter()
.find(|p| p.scopes == grant.granted_scopes.as_str());
preset
@@ -1622,7 +1622,7 @@ pub async fn consent_post(
};
let effective_scope_str = if let Some(ref grant) = delegation_grant {
crate::delegation::intersect_scopes(original_scope_str, grant.granted_scopes.as_str())
tranquil_pds::delegation::intersect_scopes(original_scope_str, grant.granted_scopes.as_str())
} else {
original_scope_str.to_string()
};
@@ -1955,7 +1955,7 @@ pub async fn authorize_2fa_post(
);
}
};
if !crate::api::server::has_totp_enabled(&state, &did).await {
if !tranquil_api::server::has_totp_enabled(&state, &did).await {
return json_error(
StatusCode::BAD_REQUEST,
"invalid_request",
@@ -1973,7 +1973,7 @@ pub async fn authorize_2fa_post(
}
};
let totp_valid =
crate::api::server::verify_totp_or_backup_for_user(&state, &did, &form.code).await;
tranquil_api::server::verify_totp_or_backup_for_user(&state, &did, &form.code).await;
if !totp_valid {
return json_error(
StatusCode::FORBIDDEN,
@@ -2011,7 +2011,7 @@ pub async fn authorize_2fa_post(
.oauth_repo
.upsert_account_device(&did, &trust_device_id)
.await;
let _ = crate::api::server::trust_device(state.oauth_repo.as_ref(), &trust_device_id).await;
let _ = tranquil_api::server::trust_device(state.oauth_repo.as_ref(), &trust_device_id).await;
}
let requested_scope_str = request_data
.parameters
@@ -2110,7 +2110,7 @@ pub async fn check_user_has_passkeys(
.await;
let has_passkeys = match user {
Ok(Some(u)) => crate::api::server::has_passkeys_for_user(&state, &u.did).await,
Ok(Some(u)) => tranquil_api::server::has_passkeys_for_user(&state, &u.did).await,
_ => false,
};
@@ -2149,8 +2149,8 @@ pub async fn check_user_security_status(
Option<String>,
) = match user {
Ok(Some(u)) => {
let passkeys = crate::api::server::has_passkeys_for_user(&state, &u.did).await;
let totp = crate::api::server::has_totp_enabled(&state, &u.did).await;
let passkeys = tranquil_api::server::has_passkeys_for_user(&state, &u.did).await;
let totp = tranquil_api::server::has_totp_enabled(&state, &u.did).await;
let has_pw = u.password_hash.is_some();
let has_controllers = state
.delegation_repo
@@ -2297,7 +2297,7 @@ pub async fn passkey_start(
let is_verified = user.channel_verification.has_any_verified();
if !is_verified {
let resend_info = crate::api::server::auto_resend_verification(&state, &user.did).await;
let resend_info = tranquil_api::server::auto_resend_verification(&state, &user.did).await;
return (
StatusCode::FORBIDDEN,
Json(serde_json::json!({
@@ -3125,14 +3125,14 @@ pub async fn authorize_passkey_finish(
if has_totp {
let device_cookie = extract_device_cookie(&headers);
let device_is_trusted = if let Some(ref dev_id) = device_cookie {
crate::api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &did).await
tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &did).await
} else {
false
};
if device_is_trusted {
if let Some(ref dev_id) = device_cookie {
let _ = crate::api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id)
let _ = tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id)
.await;
}
} else {
@@ -3395,7 +3395,7 @@ pub async fn register_complete(
};
if !is_verified {
let resend_info = crate::api::server::auto_resend_verification(&state, &did).await;
let resend_info = tranquil_api::server::auto_resend_verification(&state, &did).await;
return (
StatusCode::FORBIDDEN,
Json(serde_json::json!({
@@ -3503,7 +3503,7 @@ pub async fn register_complete(
pub async fn establish_session(
State(state): State<AppState>,
headers: HeaderMap,
auth: crate::auth::Auth<crate::auth::Active>,
auth: tranquil_pds::auth::Auth<tranquil_pds::auth::Active>,
) -> Response {
let did = &auth.did;
@@ -1,9 +1,9 @@
use crate::auth::{Active, Auth};
use crate::delegation::DelegationActionType;
use crate::rate_limit::{LoginLimit, OAuthRateLimited, TotpVerifyLimit};
use crate::state::AppState;
use crate::types::PlainPassword;
use crate::util::extract_client_ip;
use tranquil_pds::auth::{Active, Auth};
use tranquil_pds::delegation::DelegationActionType;
use tranquil_pds::rate_limit::{LoginLimit, OAuthRateLimited, TotpVerifyLimit};
use tranquil_pds::state::AppState;
use tranquil_pds::types::PlainPassword;
use tranquil_pds::util::extract_client_ip;
use axum::{
Json,
extract::State,
@@ -220,7 +220,7 @@ pub async fn delegation_auth(
.into_response();
}
let has_totp = crate::api::server::has_totp_enabled(&state, &controller_did).await;
let has_totp = tranquil_api::server::has_totp_enabled(&state, &controller_did).await;
if has_totp {
return Json(DelegationAuthResponse {
success: true,
@@ -377,7 +377,7 @@ pub async fn delegation_totp_verify(
};
let totp_valid =
crate::api::server::verify_totp_or_backup_for_user(&state, &controller_did, &form.code)
tranquil_api::server::verify_totp_or_backup_for_user(&state, &controller_did, &form.code)
.await;
if !totp_valid {
return Json(DelegationAuthResponse {
@@ -1,7 +1,7 @@
use std::fmt::Debug;
use crate::oauth::jwks::{JwkSet, create_jwk_set};
use crate::state::AppState;
use crate::jwks::{JwkSet, create_jwk_set};
use tranquil_pds::state::AppState;
use axum::{Json, extract::State};
use http::{HeaderName, header};
use serde::{Deserialize, Serialize};
@@ -129,8 +129,8 @@ pub async fn oauth_authorization_server(
}
pub async fn oauth_jwks(State(_state): State<AppState>) -> Json<JwkSet> {
use crate::config::AuthConfig;
use crate::oauth::jwks::Jwk;
use tranquil_pds::config::AuthConfig;
use crate::jwks::Jwk;
let config = AuthConfig::get();
let server_key = Jwk {
kty: "EC".to_string(),
@@ -1,10 +1,10 @@
use crate::oauth::{
use tranquil_pds::oauth::{
AuthorizationRequestParameters, ClientAuth, ClientMetadataCache, CodeChallengeMethod,
OAuthError, Prompt, RequestData, RequestId, ResponseMode, ResponseType,
scopes::{ParsedScope, parse_scope},
};
use crate::rate_limit::{OAuthParLimit, OAuthRateLimited};
use crate::state::AppState;
use tranquil_pds::rate_limit::{OAuthParLimit, OAuthRateLimited};
use tranquil_pds::state::AppState;
use axum::body::Bytes;
use axum::{Json, extract::State, http::HeaderMap};
use chrono::{Duration, Utc};
@@ -118,7 +118,7 @@ pub async fn pushed_authorization_request(
.oauth_repo
.create_authorization_request(&request_id_typed, &request_data)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
tokio::spawn({
let oauth_repo = state.oauth_repo.clone();
async move {
@@ -159,7 +159,7 @@ fn determine_client_auth(request: &ParRequest) -> Result<ClientAuth, OAuthError>
fn validate_scope(
requested_scope: &Option<String>,
client_metadata: &crate::oauth::ClientMetadata,
client_metadata: &tranquil_pds::oauth::ClientMetadata,
) -> Result<Option<String>, OAuthError> {
let scope_str = match requested_scope {
Some(s) if !s.is_empty() => s,
@@ -2,16 +2,16 @@ use super::helpers::{create_access_token_with_delegation, verify_pkce};
use super::types::{
RequestClientAuth, TokenGrant, TokenResponse, TokenType, ValidatedTokenRequest,
};
use crate::config::AuthConfig;
use crate::delegation::intersect_scopes;
use crate::oauth::{
use tranquil_pds::config::AuthConfig;
use tranquil_pds::delegation::intersect_scopes;
use tranquil_pds::oauth::{
AuthFlow, ClientAuth, ClientMetadataCache, DPoPVerifier, OAuthError, RefreshToken, TokenData,
TokenId,
db::{enforce_token_limit_for_user, lookup_refresh_token},
scopes::expand_include_scopes,
verify_client_auth,
};
use crate::state::AppState;
use tranquil_pds::state::AppState;
use axum::Json;
use axum::http::{HeaderMap, Method};
use chrono::{Duration, Utc};
@@ -50,7 +50,7 @@ pub async fn handle_authorization_code_grant(
.oauth_repo
.consume_authorization_request_by_code(&auth_code)
.await
.map_err(crate::oauth::db_err_to_oauth)?
.map_err(tranquil_pds::oauth::db_err_to_oauth)?
.ok_or_else(|| OAuthError::InvalidGrant("Invalid or expired code".to_string()))?;
let flow = AuthFlow::from_request_data(auth_request)
@@ -107,7 +107,7 @@ pub async fn handle_authorization_code_grant(
.oauth_repo
.check_and_record_dpop_jti(&result.jti)
.await
.map_err(crate::oauth::db_err_to_oauth)?
.map_err(tranquil_pds::oauth::db_err_to_oauth)?
{
return Err(OAuthError::InvalidDpopProof(
"DPoP proof has already been used".to_string(),
@@ -201,7 +201,7 @@ pub async fn handle_authorization_code_grant(
.oauth_repo
.create_token(&token_data)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
tracing::info!(
did = %did,
token_id = %token_id.0,
@@ -321,7 +321,7 @@ pub async fn handle_refresh_token_grant(
.oauth_repo
.delete_token_family(original_token_id)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
return Err(OAuthError::InvalidGrant(
"Refresh token reuse detected, token family revoked".to_string(),
));
@@ -332,7 +332,7 @@ pub async fn handle_refresh_token_grant(
.oauth_repo
.delete_token_family(db_id)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
return Err(OAuthError::InvalidGrant(
"Refresh token has expired".to_string(),
));
@@ -354,7 +354,7 @@ pub async fn handle_refresh_token_grant(
.oauth_repo
.check_and_record_dpop_jti(&result.jti)
.await
.map_err(crate::oauth::db_err_to_oauth)?
.map_err(tranquil_pds::oauth::db_err_to_oauth)?
{
return Err(OAuthError::InvalidDpopProof(
"DPoP proof has already been used".to_string(),
@@ -387,7 +387,7 @@ pub async fn handle_refresh_token_grant(
.oauth_repo
.rotate_token(db_id, &new_refresh_typed, new_expires_at)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
tracing::info!(
did = %token_data.did,
new_expires_at = %new_expires_at,
@@ -1,5 +1,5 @@
use crate::config::AuthConfig;
use crate::oauth::OAuthError;
use tranquil_pds::config::AuthConfig;
use tranquil_pds::oauth::OAuthError;
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::Utc;
@@ -1,7 +1,7 @@
use super::helpers::extract_token_claims;
use crate::oauth::OAuthError;
use crate::rate_limit::{OAuthIntrospectLimit, OAuthRateLimited};
use crate::state::AppState;
use tranquil_pds::oauth::OAuthError;
use tranquil_pds::rate_limit::{OAuthIntrospectLimit, OAuthRateLimited};
use tranquil_pds::state::AppState;
use axum::extract::State;
use axum::http::StatusCode;
use axum::{Form, Json};
@@ -27,20 +27,20 @@ pub async fn revoke_token(
.oauth_repo
.get_token_by_refresh_token(&refresh_token)
.await
.map_err(crate::oauth::db_err_to_oauth)?
.map_err(tranquil_pds::oauth::db_err_to_oauth)?
{
state
.oauth_repo
.delete_token_family(db_id)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
} else {
let token_id = TokenId::from(token.clone());
state
.oauth_repo
.delete_token(&token_id)
.await
.map_err(crate::oauth::db_err_to_oauth)?;
.map_err(tranquil_pds::oauth::db_err_to_oauth)?;
}
}
Ok(StatusCode::OK)
@@ -3,9 +3,9 @@ mod helpers;
mod introspect;
mod types;
use crate::oauth::OAuthError;
use crate::rate_limit::{OAuthRateLimited, OAuthTokenLimit};
use crate::state::AppState;
use tranquil_pds::oauth::OAuthError;
use tranquil_pds::rate_limit::{OAuthRateLimited, OAuthTokenLimit};
use tranquil_pds::state::AppState;
use axum::body::Bytes;
use axum::{Json, extract::State, http::HeaderMap};
@@ -41,7 +41,7 @@ pub async fn token_endpoint(
));
};
let dpop_proof = headers
.get(crate::util::HEADER_DPOP)
.get(tranquil_pds::util::HEADER_DPOP)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
let validated = request.validate()?;
@@ -1,4 +1,4 @@
use crate::oauth::OAuthError;
use tranquil_pds::oauth::OAuthError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
+116
View File
@@ -0,0 +1,116 @@
pub mod endpoints;
pub mod jwks;
pub mod sso_endpoints;
use tranquil_pds::state::AppState;
pub fn oauth_routes() -> axum::Router<AppState> {
use axum::{middleware, routing::{get, post}};
axum::Router::new()
.route("/jwks", get(endpoints::oauth_jwks))
.route("/par", post(endpoints::pushed_authorization_request))
.route("/authorize", get(endpoints::authorize_get))
.route("/authorize", post(endpoints::authorize_post))
.route(
"/authorize/accounts",
get(endpoints::authorize_accounts),
)
.route(
"/authorize/select",
post(endpoints::authorize_select),
)
.route("/authorize/2fa", get(endpoints::authorize_2fa_get))
.route("/authorize/2fa", post(endpoints::authorize_2fa_post))
.route(
"/authorize/passkey",
get(endpoints::authorize_passkey_start),
)
.route(
"/authorize/passkey",
post(endpoints::authorize_passkey_finish),
)
.route(
"/passkey/check",
get(endpoints::check_user_has_passkeys),
)
.route(
"/security-status",
get(endpoints::check_user_security_status),
)
.route("/passkey/start", post(endpoints::passkey_start))
.route("/passkey/finish", post(endpoints::passkey_finish))
.route("/authorize/deny", post(endpoints::authorize_deny))
.route(
"/register/complete",
post(endpoints::register_complete),
)
.route(
"/establish-session",
post(endpoints::establish_session),
)
.route("/authorize/consent", get(endpoints::consent_get))
.route("/authorize/consent", post(endpoints::consent_post))
.route("/authorize/renew", post(endpoints::authorize_renew))
.route(
"/authorize/redirect",
get(endpoints::authorize_redirect),
)
.route("/delegation/auth", post(endpoints::delegation_auth))
.route(
"/delegation/auth-token",
post(endpoints::delegation_auth_token),
)
.route(
"/delegation/totp",
post(endpoints::delegation_totp_verify),
)
.route("/token", post(endpoints::token_endpoint))
.route("/revoke", post(endpoints::revoke_token))
.route("/introspect", post(endpoints::introspect_token))
.route("/sso/providers", get(sso_endpoints::get_sso_providers))
.route("/sso/initiate", post(sso_endpoints::sso_initiate))
.route(
"/sso/callback",
get(sso_endpoints::sso_callback).post(sso_endpoints::sso_callback_post),
)
.route("/sso/linked", get(sso_endpoints::get_linked_accounts))
.route("/sso/unlink", post(sso_endpoints::unlink_account))
.route(
"/sso/pending-registration",
get(sso_endpoints::get_pending_registration),
)
.route(
"/sso/complete-registration",
post(sso_endpoints::complete_registration),
)
.route(
"/sso/check-handle-available",
get(sso_endpoints::check_handle_available),
)
.layer(middleware::from_fn(tranquil_pds::oauth::verify::dpop_nonce_middleware))
}
pub fn well_known_oauth_routes() -> axum::Router<AppState> {
use axum::routing::get;
axum::Router::new()
.route(
"/oauth-protected-resource",
get(endpoints::oauth_protected_resource),
)
.route(
"/oauth-authorization-server",
get(endpoints::oauth_authorization_server),
)
}
pub fn frontend_client_metadata_route() -> axum::Router<AppState> {
use axum::routing::get;
axum::Router::new()
.route(
"/oauth-client-metadata.json",
get(endpoints::frontend_client_metadata),
)
}
@@ -9,15 +9,15 @@ use serde::{Deserialize, Serialize};
use tranquil_db_traits::{SsoAction, SsoProviderType};
use tranquil_types::RequestId;
use super::config::SsoConfig;
use crate::api::error::ApiError;
use crate::auth::extractor::extract_auth_token_from_header;
use crate::auth::{generate_app_password, validate_bearer_token_cached};
use crate::rate_limit::{
use tranquil_pds::sso::SsoConfig;
use tranquil_pds::api::error::ApiError;
use tranquil_pds::auth::extractor::extract_auth_token_from_header;
use tranquil_pds::auth::{generate_app_password, validate_bearer_token_cached};
use tranquil_pds::rate_limit::{
AccountCreationLimit, RateLimited, SsoCallbackLimit, SsoInitiateLimit, SsoUnlinkLimit,
check_user_rate_limit_with_message,
};
use crate::state::AppState;
use tranquil_pds::state::AppState;
fn generate_state() -> String {
use rand::RngCore;
@@ -367,7 +367,7 @@ async fn handle_sso_login(
state: &AppState,
request_uri: &str,
provider: SsoProviderType,
user_info: &crate::sso::providers::SsoUserInfo,
user_info: &tranquil_pds::sso::providers::SsoUserInfo,
) -> Response {
let identity = match state
.sso_repo
@@ -482,7 +482,7 @@ async fn handle_sso_link(
state: &AppState,
did: tranquil_types::Did,
provider: SsoProviderType,
user_info: &crate::sso::providers::SsoUserInfo,
user_info: &tranquil_pds::sso::providers::SsoUserInfo,
) -> Response {
let existing = state
.sso_repo
@@ -555,7 +555,7 @@ async fn handle_sso_register(
state: &AppState,
request_uri: &str,
provider: SsoProviderType,
user_info: &crate::sso::providers::SsoUserInfo,
user_info: &tranquil_pds::sso::providers::SsoUserInfo,
) -> Response {
match state
.sso_repo
@@ -616,7 +616,7 @@ pub struct LinkedAccountsResponse {
pub async fn get_linked_accounts(
State(state): State<AppState>,
auth: crate::auth::Auth<crate::auth::Active>,
auth: tranquil_pds::auth::Auth<tranquil_pds::auth::Active>,
) -> Result<Json<LinkedAccountsResponse>, ApiError> {
let identities = state
.sso_repo
@@ -651,7 +651,7 @@ pub struct UnlinkAccountResponse {
pub async fn unlink_account(
State(state): State<AppState>,
auth: crate::auth::Auth<crate::auth::Active>,
auth: tranquil_pds::auth::Auth<tranquil_pds::auth::Active>,
Json(input): Json<UnlinkAccountRequest>,
) -> Result<Json<UnlinkAccountResponse>, ApiError> {
let _rate_limit = check_user_rate_limit_with_message::<SsoUnlinkLimit>(
@@ -763,7 +763,7 @@ pub async fn check_handle_available(
}));
}
let validated = match crate::api::validation::validate_short_handle(&query.handle) {
let validated = match tranquil_pds::api::validation::validate_short_handle(&query.handle) {
Ok(h) => h,
Err(e) => {
return Ok(Json(CheckHandleResponse {
@@ -781,7 +781,7 @@ pub async fn check_handle_available(
}
let domain = query.domain.as_deref().unwrap_or(&available_domains[0]);
let full_handle = format!("{}.{}", validated, domain);
let handle_typed: crate::types::Handle = match full_handle.parse() {
let handle_typed: tranquil_pds::types::Handle = match full_handle.parse() {
Ok(h) => h,
Err(_) => return Err(ApiError::InvalidHandle(None)),
};
@@ -879,12 +879,12 @@ pub async fn complete_registration(
.unwrap_or(&input.handle),
None => &input.handle,
};
match crate::api::validation::validate_short_handle(handle_to_validate) {
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
Ok(h) => format!("{}.{}", h, matched_domain.unwrap_or(&available_domains[0])),
Err(_) => return Err(ApiError::InvalidHandle(None)),
}
} else {
match crate::api::validation::validate_full_domain_handle(&input.handle) {
match tranquil_pds::api::validation::validate_full_domain_handle(&input.handle) {
Ok(h) => h,
Err(_) => return Err(ApiError::InvalidHandle(None)),
}
@@ -914,7 +914,7 @@ pub async fn complete_registration(
tranquil_db_traits::CommsChannel::Discord => match &input.discord_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().to_lowercase();
if !crate::api::validation::is_valid_discord_username(&clean) {
if !tranquil_pds::api::validation::is_valid_discord_username(&clean) {
return Err(ApiError::InvalidRequest(
"Invalid Discord username. Must be 2-32 lowercase characters (letters, numbers, underscores, periods)".into(),
));
@@ -926,7 +926,7 @@ pub async fn complete_registration(
tranquil_db_traits::CommsChannel::Telegram => match &input.telegram_username {
Some(username) if !username.trim().is_empty() => {
let clean = username.trim().trim_start_matches('@');
if !crate::api::validation::is_valid_telegram_username(clean) {
if !tranquil_pds::api::validation::is_valid_telegram_username(clean) {
return Err(ApiError::InvalidRequest(
"Invalid Telegram username. Must be 5-32 characters, alphanumeric or underscore".into(),
));
@@ -960,7 +960,7 @@ pub async fn complete_registration(
if e.len() > 254 {
return Err(ApiError::InvalidEmail);
}
if !crate::api::validation::is_valid_email(e) {
if !tranquil_pds::api::validation::is_valid_email(e) {
return Err(ApiError::InvalidEmail);
}
Some(e.clone())
@@ -981,7 +981,7 @@ pub async fn complete_registration(
None
};
let handle_typed: crate::types::Handle =
let handle_typed: tranquil_pds::types::Handle =
handle.parse().map_err(|_| ApiError::InvalidHandle(None))?;
let reserved = state
.user_repo
@@ -1008,7 +1008,7 @@ pub async fn complete_registration(
let did = match did_type {
"web" => {
if !crate::api::server::meta::is_self_hosted_did_web_enabled() {
if !tranquil_pds::util::is_self_hosted_did_web_enabled() {
return Err(ApiError::SelfHostedDidWebDisabled);
}
let encoded_handle = handle.replace(':', "%3A");
@@ -1038,9 +1038,9 @@ pub async fn complete_registration(
.secrets
.plc_rotation_key
.clone()
.unwrap_or_else(|| crate::plc::signing_key_to_did_key(&signing_key));
.unwrap_or_else(|| tranquil_pds::plc::signing_key_to_did_key(&signing_key));
let genesis_result = match crate::plc::create_genesis_operation(
let genesis_result = match tranquil_pds::plc::create_genesis_operation(
&signing_key,
&rotation_key,
&handle,
@@ -1055,7 +1055,7 @@ pub async fn complete_registration(
}
};
let plc_client = crate::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone()));
if let Err(e) = plc_client
.send_operation(&genesis_result.did, &genesis_result.signed_operation)
.await
@@ -1071,7 +1071,7 @@ pub async fn complete_registration(
};
tracing::info!(did = %did, handle = %handle, provider = %pending_preview.provider.as_str(), "Created DID for SSO account");
let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) {
let encrypted_key_bytes = match tranquil_pds::config::encrypt_key(&secret_key_bytes) {
Ok(bytes) => bytes,
Err(e) => {
tracing::error!("Error encrypting signing key: {:?}", e);
@@ -1089,10 +1089,10 @@ pub async fn complete_registration(
};
let rev = Tid::now(LimitedU32::MIN);
let did_typed: crate::types::Did = did
let did_typed: tranquil_pds::types::Did = did
.parse()
.map_err(|_| ApiError::InternalError(Some("Invalid DID".into())))?;
let (commit_bytes, _sig) = match crate::api::repo::record::utils::create_signed_commit(
let (commit_bytes, _sig) = match tranquil_pds::repo_ops::create_signed_commit(
&did_typed,
mst_root,
rev.as_ref(),
@@ -1146,7 +1146,7 @@ pub async fn complete_registration(
.map(|s| s.trim().trim_start_matches('@').to_lowercase())
.filter(|s| !s.is_empty()),
encrypted_key_bytes: encrypted_key_bytes.clone(),
encryption_version: crate::config::ENCRYPTION_VERSION,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
commit_cid: commit_cid.to_string(),
repo_rev: rev.as_ref().to_string(),
genesis_block_cids,
@@ -1189,12 +1189,12 @@ pub async fn complete_registration(
.await;
if let Err(e) =
crate::api::repo::record::sequence_identity_event(&state, &did_typed, Some(&handle_typed))
tranquil_pds::repo_ops::sequence_identity_event(&state, &did_typed, Some(&handle_typed))
.await
{
tracing::warn!("Failed to sequence identity event for {}: {}", did, e);
}
if let Err(e) = crate::api::repo::record::sequence_account_event(
if let Err(e) = tranquil_pds::repo_ops::sequence_account_event(
&state,
&did_typed,
tranquil_db_traits::AccountStatus::Active,
@@ -1208,11 +1208,11 @@ pub async fn complete_registration(
"$type": "app.bsky.actor.profile",
"displayName": handle_typed.as_str()
});
if let Err(e) = crate::api::repo::record::create_record_internal(
if let Err(e) = tranquil_pds::repo_ops::create_record_internal(
&state,
&did_typed,
&crate::types::PROFILE_COLLECTION,
&crate::types::PROFILE_RKEY,
&tranquil_pds::types::PROFILE_COLLECTION,
&tranquil_pds::types::PROFILE_RKEY,
&profile_record,
)
.await
@@ -1287,9 +1287,9 @@ pub async fn complete_registration(
tracing::info!(did = %did, "Auto-verified email from SSO provider");
if is_standalone {
let key_bytes = match crate::config::decrypt_key(
let key_bytes = match tranquil_pds::config::decrypt_key(
&encrypted_key_bytes,
Some(crate::config::ENCRYPTION_VERSION),
Some(tranquil_pds::config::ENCRYPTION_VERSION),
) {
Ok(k) => k,
Err(e) => {
@@ -1298,7 +1298,7 @@ pub async fn complete_registration(
}
};
let access_meta = match crate::auth::create_access_token_with_metadata(&did, &key_bytes)
let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&did, &key_bytes)
{
Ok(m) => m,
Err(e) => {
@@ -1307,7 +1307,7 @@ pub async fn complete_registration(
}
};
let refresh_meta =
match crate::auth::create_refresh_token_with_metadata(&did, &key_bytes) {
match tranquil_pds::auth::create_refresh_token_with_metadata(&did, &key_bytes) {
Ok(m) => m,
Err(e) => {
tracing::error!("Failed to create refresh token: {:?}", e);
@@ -1333,7 +1333,7 @@ pub async fn complete_registration(
}
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = crate::comms::comms_repo::enqueue_welcome(
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_welcome(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
user_id.unwrap_or(uuid::Uuid::nil()),
@@ -1370,14 +1370,14 @@ pub async fn complete_registration(
}
if let Some(uid) = user_id {
let verification_token = crate::auth::verification_token::generate_signup_token(
let verification_token = tranquil_pds::auth::verification_token::generate_signup_token(
&did_typed,
verification_channel,
&verification_recipient,
);
let formatted_token =
crate::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = crate::comms::comms_repo::enqueue_signup_verification(
tranquil_pds::auth::verification_token::format_token_for_display(&verification_token);
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification(
state.user_repo.as_ref(),
state.infra_repo.as_ref(),
uid,
+3 -3
View File
@@ -31,8 +31,6 @@ bs58 = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
cid = { workspace = true }
clap = { workspace = true }
dotenvy = { workspace = true }
ed25519-dalek = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
@@ -75,7 +73,6 @@ tower = { workspace = true }
tower-http = { workspace = true }
tower-layer = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
urlencoding = { workspace = true }
uuid = { workspace = true }
webauthn-rs = { workspace = true }
@@ -97,4 +94,7 @@ ctor = { workspace = true }
testcontainers = { workspace = true }
testcontainers-modules = { workspace = true }
tranquil-ripple = { workspace = true }
tranquil-sync = { workspace = true }
tranquil-api = { workspace = true }
tranquil-oauth-server = { workspace = true }
wiremock = { workspace = true }
-14
View File
@@ -1,22 +1,8 @@
pub mod actor;
pub mod admin;
pub mod age_assurance;
pub mod backup;
pub mod delegation;
pub mod discord_webhook;
pub mod error;
pub mod identity;
pub mod moderation;
pub mod notification_prefs;
pub mod proxy;
pub mod proxy_client;
pub mod repo;
pub mod responses;
pub mod server;
pub mod telegram_webhook;
pub mod temp;
pub mod validation;
pub mod verification;
pub use error::ApiError;
pub use proxy_client::{AtUriParts, proxy_client, validate_at_uri, validate_limit};
+3 -3
View File
@@ -74,7 +74,7 @@ pub async fn require_legacy_session_mfa<'a>(
state: &AppState,
user: &'a AuthenticatedUser,
) -> Result<MfaVerified<'a>, Response> {
use crate::api::server::reauth::{check_legacy_session_mfa, legacy_mfa_required_response};
use crate::auth::reauth::{check_legacy_session_mfa, legacy_mfa_required_response};
if check_legacy_session_mfa(&*state.session_repo, &user.did).await {
Ok(MfaVerified::from_session_reauth(user))
@@ -87,7 +87,7 @@ pub async fn require_reauth_window<'a>(
state: &AppState,
user: &'a AuthenticatedUser,
) -> Result<MfaVerified<'a>, Response> {
use crate::api::server::reauth::{REAUTH_WINDOW_SECONDS, reauth_required_response};
use crate::auth::reauth::{REAUTH_WINDOW_SECONDS, reauth_required_response};
use chrono::Utc;
let status = state
@@ -117,7 +117,7 @@ pub async fn require_reauth_window_if_available<'a>(
state: &AppState,
user: &'a AuthenticatedUser,
) -> Result<Option<MfaVerified<'a>>, Response> {
use crate::api::server::reauth::{check_reauth_required_cached, reauth_required_response};
use crate::auth::reauth::{check_reauth_required_cached, reauth_required_response};
let has_password = state
.user_repo
+1
View File
@@ -13,6 +13,7 @@ use tranquil_db_traits::OAuthRepository;
pub mod account_verified;
pub mod email_token;
pub mod extractor;
pub mod reauth;
pub mod legacy_2fa;
pub mod login_identifier;
pub mod mfa_verified;
+156
View File
@@ -0,0 +1,156 @@
use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use chrono::Utc;
use serde::Serialize;
use tranquil_db_traits::{SessionRepository, UserRepository};
pub const REAUTH_WINDOW_SECONDS: i64 = 300;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ReauthMethod {
Password,
Totp,
Passkey,
}
fn is_reauth_required(last_reauth_at: Option<chrono::DateTime<Utc>>) -> bool {
match last_reauth_at {
None => true,
Some(t) => {
let elapsed = Utc::now().signed_duration_since(t);
elapsed.num_seconds() > REAUTH_WINDOW_SECONDS
}
}
}
async fn get_available_reauth_methods(
user_repo: &dyn UserRepository,
_session_repo: &dyn SessionRepository,
did: &crate::types::Did,
) -> Vec<ReauthMethod> {
let mut methods = Vec::new();
let has_password = user_repo
.get_password_hash_by_did(did)
.await
.ok()
.flatten()
.is_some();
if has_password {
methods.push(ReauthMethod::Password);
}
let has_totp = user_repo.has_totp_enabled(did).await.unwrap_or(false);
if has_totp {
methods.push(ReauthMethod::Totp);
}
let has_passkeys = user_repo.has_passkeys(did).await.unwrap_or(false);
if has_passkeys {
methods.push(ReauthMethod::Passkey);
}
methods
}
pub async fn check_reauth_required_cached(
session_repo: &dyn SessionRepository,
cache: &std::sync::Arc<dyn crate::cache::Cache>,
did: &crate::types::Did,
) -> bool {
let cache_key = crate::cache_keys::reauth_key(did);
if let Some(timestamp_str) = cache.get(&cache_key).await
&& let Ok(timestamp) = timestamp_str.parse::<i64>()
{
let reauth_time = chrono::DateTime::from_timestamp(timestamp, 0);
if let Some(t) = reauth_time {
let elapsed = Utc::now().signed_duration_since(t);
if elapsed.num_seconds() <= REAUTH_WINDOW_SECONDS {
return false;
}
}
}
match session_repo.get_last_reauth_at(did).await {
Ok(last_reauth_at) => is_reauth_required(last_reauth_at),
_ => true,
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReauthRequiredError {
pub error: String,
pub message: String,
pub reauth_methods: Vec<ReauthMethod>,
}
pub async fn reauth_required_response(
user_repo: &dyn UserRepository,
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
) -> Response {
let methods = get_available_reauth_methods(user_repo, session_repo, did).await;
(
StatusCode::UNAUTHORIZED,
Json(ReauthRequiredError {
error: "ReauthRequired".to_string(),
message: "Re-authentication required for this action".to_string(),
reauth_methods: methods,
}),
)
.into_response()
}
pub async fn check_legacy_session_mfa(
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
) -> bool {
match session_repo.get_session_mfa_status(did).await {
Ok(Some(status)) => {
if status.login_type.is_modern() {
return true;
}
if status.mfa_verified {
return true;
}
if let Some(last_reauth) = status.last_reauth_at {
let elapsed = chrono::Utc::now().signed_duration_since(last_reauth);
if elapsed.num_seconds() <= REAUTH_WINDOW_SECONDS {
return true;
}
}
false
}
_ => true,
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MfaVerificationRequiredError {
pub error: String,
pub message: String,
pub reauth_methods: Vec<ReauthMethod>,
}
pub async fn legacy_mfa_required_response(
user_repo: &dyn UserRepository,
session_repo: &dyn SessionRepository,
did: &crate::types::Did,
) -> Response {
let methods = get_available_reauth_methods(user_repo, session_repo, did).await;
(
StatusCode::FORBIDDEN,
Json(MfaVerificationRequiredError {
error: "MfaVerificationRequired".to_string(),
message: "This sensitive operation requires MFA verification. Your session was created via a legacy app that doesn't support MFA during login.".to_string(),
reauth_methods: methods,
}),
)
.into_response()
}
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::circuit_breaker::CircuitBreaker;
use crate::sync::firehose::SequencedEvent;
use tranquil_db_traits::SequencedEvent;
use reqwest::Client;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
+28 -601
View File
@@ -17,6 +17,7 @@ pub mod oauth;
pub mod plc;
pub mod rate_limit;
pub mod repo;
pub mod repo_ops;
pub mod repo_write_lock;
pub mod scheduled;
pub mod sso;
@@ -33,7 +34,7 @@ use axum::{
extract::DefaultBodyLimit,
http::Method,
middleware,
routing::{get, post},
routing::get,
};
use http::StatusCode;
use serde_json::json;
@@ -56,496 +57,30 @@ pub const BUILD_VERSION: &str = concat!(
#[cfg(not(debug_assertions))]
pub const BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct ExternalRoutes {
pub xrpc: Router<AppState>,
pub oauth: Router<AppState>,
pub well_known: Router<AppState>,
pub extra: Router<AppState>,
}
impl Default for ExternalRoutes {
fn default() -> Self {
Self {
xrpc: Router::new(),
oauth: Router::new(),
well_known: Router::new(),
extra: Router::new(),
}
}
}
pub fn app(state: AppState) -> Router {
let xrpc_router = Router::new()
.route("/_health", get(api::server::health))
.route(
"/com.atproto.server.describeServer",
get(api::server::describe_server),
)
.route(
"/com.atproto.server.createAccount",
post(api::identity::create_account),
)
.route(
"/com.atproto.server.createSession",
post(api::server::create_session),
)
.route(
"/com.atproto.server.getSession",
get(api::server::get_session),
)
.route("/_account.listSessions", get(api::server::list_sessions))
.route("/_account.revokeSession", post(api::server::revoke_session))
.route(
"/_account.revokeAllSessions",
post(api::server::revoke_all_sessions),
)
.route(
"/com.atproto.server.deleteSession",
post(api::server::delete_session),
)
.route(
"/com.atproto.server.refreshSession",
post(api::server::refresh_session),
)
.route(
"/com.atproto.server.confirmSignup",
post(api::server::confirm_signup),
)
.route(
"/com.atproto.server.resendVerification",
post(api::server::resend_verification),
)
.route(
"/com.atproto.server.getServiceAuth",
get(api::server::get_service_auth),
)
.route(
"/com.atproto.identity.resolveHandle",
get(api::identity::resolve_handle),
)
.route(
"/com.atproto.repo.createRecord",
post(api::repo::create_record),
)
.route("/com.atproto.repo.putRecord", post(api::repo::put_record))
.route("/com.atproto.repo.getRecord", get(api::repo::get_record))
.route(
"/com.atproto.repo.deleteRecord",
post(api::repo::delete_record),
)
.route(
"/com.atproto.repo.listRecords",
get(api::repo::list_records),
)
.route(
"/com.atproto.repo.describeRepo",
get(api::repo::describe_repo),
)
.route("/com.atproto.repo.uploadBlob", post(api::repo::upload_blob))
.route(
"/com.atproto.repo.applyWrites",
post(api::repo::apply_writes),
)
.route(
"/com.atproto.sync.getLatestCommit",
get(sync::get_latest_commit),
)
.route("/com.atproto.sync.listRepos", get(sync::list_repos))
.route("/com.atproto.sync.getBlob", get(sync::get_blob))
.route("/com.atproto.sync.listBlobs", get(sync::list_blobs))
.route(
"/com.atproto.sync.getRepoStatus",
get(sync::get_repo_status),
)
.route(
"/com.atproto.server.checkAccountStatus",
get(api::server::check_account_status),
)
.route(
"/com.atproto.identity.getRecommendedDidCredentials",
get(api::identity::get_recommended_did_credentials),
)
.route(
"/com.atproto.repo.listMissingBlobs",
get(api::repo::list_missing_blobs),
)
.route(
"/com.atproto.sync.notifyOfUpdate",
post(sync::notify_of_update),
)
.route("/com.atproto.sync.requestCrawl", post(sync::request_crawl))
.route("/com.atproto.sync.getBlocks", get(sync::get_blocks))
.route("/com.atproto.sync.getRepo", get(sync::get_repo))
.route("/com.atproto.sync.getRecord", get(sync::get_record))
.route(
"/com.atproto.sync.subscribeRepos",
get(sync::subscribe_repos),
)
.route("/com.atproto.sync.getHead", get(sync::get_head))
.route("/com.atproto.sync.getCheckout", get(sync::get_checkout))
.route(
"/com.atproto.moderation.createReport",
post(api::moderation::create_report),
)
.route(
"/com.atproto.admin.getAccountInfo",
get(api::admin::get_account_info),
)
.route(
"/com.atproto.admin.getAccountInfos",
get(api::admin::get_account_infos),
)
.route(
"/com.atproto.admin.searchAccounts",
get(api::admin::search_accounts),
)
.route(
"/com.atproto.server.activateAccount",
post(api::server::activate_account),
)
.route(
"/com.atproto.server.deactivateAccount",
post(api::server::deactivate_account),
)
.route(
"/com.atproto.server.requestAccountDelete",
post(api::server::request_account_delete),
)
.route(
"/com.atproto.server.deleteAccount",
post(api::server::delete_account),
)
.route(
"/com.atproto.server.requestPasswordReset",
post(api::server::request_password_reset),
)
.route(
"/com.atproto.server.resetPassword",
post(api::server::reset_password),
)
.route(
"/_account.changePassword",
post(api::server::change_password),
)
.route(
"/_account.removePassword",
post(api::server::remove_password),
)
.route(
"/_account.setPassword",
post(api::server::set_password),
)
.route(
"/_account.getPasswordStatus",
get(api::server::get_password_status),
)
.route(
"/_account.getReauthStatus",
get(api::server::get_reauth_status),
)
.route(
"/_account.reauthPassword",
post(api::server::reauth_password),
)
.route("/_account.reauthTotp", post(api::server::reauth_totp))
.route(
"/_account.reauthPasskeyStart",
post(api::server::reauth_passkey_start),
)
.route(
"/_account.reauthPasskeyFinish",
post(api::server::reauth_passkey_finish),
)
.route(
"/_account.getLegacyLoginPreference",
get(api::server::get_legacy_login_preference),
)
.route(
"/_account.updateLegacyLoginPreference",
post(api::server::update_legacy_login_preference),
)
.route("/_account.updateLocale", post(api::server::update_locale))
.route(
"/_account.listTrustedDevices",
get(api::server::list_trusted_devices),
)
.route(
"/_account.revokeTrustedDevice",
post(api::server::revoke_trusted_device),
)
.route(
"/_account.updateTrustedDevice",
post(api::server::update_trusted_device),
)
.route(
"/_account.createPasskeyAccount",
post(api::server::create_passkey_account),
)
.route(
"/_account.startPasskeyRegistrationForSetup",
post(api::server::start_passkey_registration_for_setup),
)
.route(
"/_account.completePasskeySetup",
post(api::server::complete_passkey_setup),
)
.route(
"/_account.requestPasskeyRecovery",
post(api::server::request_passkey_recovery),
)
.route(
"/_account.recoverPasskeyAccount",
post(api::server::recover_passkey_account),
)
.route(
"/_account.updateDidDocument",
post(api::server::update_did_document),
)
.route(
"/_account.getDidDocument",
get(api::server::get_did_document),
)
.route(
"/com.atproto.server.requestEmailUpdate",
post(api::server::request_email_update),
)
.route(
"/_checkEmailVerified",
post(api::server::check_email_verified),
)
.route(
"/_checkChannelVerified",
post(api::server::check_channel_verified),
)
.route(
"/com.atproto.server.confirmEmail",
post(api::server::confirm_email),
)
.route(
"/com.atproto.server.updateEmail",
post(api::server::update_email),
)
.route(
"/_account.authorizeEmailUpdate",
get(api::server::authorize_email_update),
)
.route(
"/_account.checkEmailUpdateStatus",
get(api::server::check_email_update_status),
)
.route(
"/_account.checkEmailInUse",
post(api::server::check_email_in_use),
)
.route(
"/_account.checkCommsChannelInUse",
post(api::server::check_comms_channel_in_use),
)
.route(
"/com.atproto.server.reserveSigningKey",
post(api::server::reserve_signing_key),
)
.route(
"/com.atproto.server.verifyMigrationEmail",
post(api::server::verify_migration_email),
)
.route(
"/com.atproto.server.resendMigrationVerification",
post(api::server::resend_migration_verification),
)
.route(
"/com.atproto.identity.updateHandle",
post(api::identity::update_handle),
)
.route(
"/com.atproto.identity.requestPlcOperationSignature",
post(api::identity::request_plc_operation_signature),
)
.route(
"/com.atproto.identity.signPlcOperation",
post(api::identity::sign_plc_operation),
)
.route(
"/com.atproto.identity.submitPlcOperation",
post(api::identity::submit_plc_operation),
)
.route(
"/_identity.verifyHandleOwnership",
post(api::identity::verify_handle_ownership),
)
.route("/com.atproto.repo.importRepo", post(api::repo::import_repo))
.route(
"/com.atproto.admin.deleteAccount",
post(api::admin::delete_account),
)
.route(
"/com.atproto.admin.updateAccountEmail",
post(api::admin::update_account_email),
)
.route(
"/com.atproto.admin.updateAccountHandle",
post(api::admin::update_account_handle),
)
.route(
"/com.atproto.admin.updateAccountPassword",
post(api::admin::update_account_password),
)
.route(
"/com.atproto.server.listAppPasswords",
get(api::server::list_app_passwords),
)
.route(
"/com.atproto.server.createAppPassword",
post(api::server::create_app_password),
)
.route(
"/com.atproto.server.revokeAppPassword",
post(api::server::revoke_app_password),
)
.route(
"/com.atproto.server.createInviteCode",
post(api::server::create_invite_code),
)
.route(
"/com.atproto.server.createInviteCodes",
post(api::server::create_invite_codes),
)
.route(
"/com.atproto.server.getAccountInviteCodes",
get(api::server::get_account_invite_codes),
)
.route(
"/com.atproto.server.createTotpSecret",
post(api::server::create_totp_secret),
)
.route(
"/com.atproto.server.enableTotp",
post(api::server::enable_totp),
)
.route(
"/com.atproto.server.disableTotp",
post(api::server::disable_totp),
)
.route(
"/com.atproto.server.getTotpStatus",
get(api::server::get_totp_status),
)
.route(
"/com.atproto.server.regenerateBackupCodes",
post(api::server::regenerate_backup_codes),
)
.route(
"/com.atproto.server.startPasskeyRegistration",
post(api::server::start_passkey_registration),
)
.route(
"/com.atproto.server.finishPasskeyRegistration",
post(api::server::finish_passkey_registration),
)
.route(
"/com.atproto.server.listPasskeys",
get(api::server::list_passkeys),
)
.route(
"/com.atproto.server.deletePasskey",
post(api::server::delete_passkey),
)
.route(
"/com.atproto.server.updatePasskey",
post(api::server::update_passkey),
)
.route(
"/com.atproto.admin.getInviteCodes",
get(api::admin::get_invite_codes),
)
.route("/_admin.getServerStats", get(api::admin::get_server_stats))
.route("/_server.getConfig", get(api::admin::get_server_config))
.route(
"/_admin.updateServerConfig",
post(api::admin::update_server_config),
)
.route(
"/com.atproto.admin.disableAccountInvites",
post(api::admin::disable_account_invites),
)
.route(
"/com.atproto.admin.enableAccountInvites",
post(api::admin::enable_account_invites),
)
.route(
"/com.atproto.admin.disableInviteCodes",
post(api::admin::disable_invite_codes),
)
.route(
"/com.atproto.admin.getSubjectStatus",
get(api::admin::get_subject_status),
)
.route(
"/com.atproto.admin.updateSubjectStatus",
post(api::admin::update_subject_status),
)
.route("/com.atproto.admin.sendEmail", post(api::admin::send_email))
.route(
"/app.bsky.actor.getPreferences",
get(api::actor::get_preferences),
)
.route(
"/app.bsky.actor.putPreferences",
post(api::actor::put_preferences),
)
.route(
"/com.atproto.temp.checkSignupQueue",
get(api::temp::check_signup_queue),
)
.route(
"/com.atproto.temp.dereferenceScope",
post(api::temp::dereference_scope),
)
.route(
"/_account.getNotificationPrefs",
get(api::notification_prefs::get_notification_prefs),
)
.route(
"/_account.updateNotificationPrefs",
post(api::notification_prefs::update_notification_prefs),
)
.route(
"/_account.getNotificationHistory",
get(api::notification_prefs::get_notification_history),
)
.route(
"/_account.confirmChannelVerification",
post(api::verification::confirm_channel_verification),
)
.route("/_account.verifyToken", post(api::server::verify_token))
.route(
"/_delegation.listControllers",
get(api::delegation::list_controllers),
)
.route(
"/_delegation.addController",
post(api::delegation::add_controller),
)
.route(
"/_delegation.removeController",
post(api::delegation::remove_controller),
)
.route(
"/_delegation.updateControllerScopes",
post(api::delegation::update_controller_scopes),
)
.route(
"/_delegation.listControlledAccounts",
get(api::delegation::list_controlled_accounts),
)
.route(
"/_delegation.getAuditLog",
get(api::delegation::get_audit_log),
)
.route(
"/_delegation.getScopePresets",
get(api::delegation::get_scope_presets),
)
.route(
"/_delegation.createDelegatedAccount",
post(api::delegation::create_delegated_account),
)
.route("/_backup.listBackups", get(api::backup::list_backups))
.route("/_backup.getBackup", get(api::backup::get_backup))
.route("/_backup.createBackup", post(api::backup::create_backup))
.route("/_backup.deleteBackup", post(api::backup::delete_backup))
.route("/_backup.setEnabled", post(api::backup::set_backup_enabled))
.route("/_backup.exportBlobs", get(api::backup::export_blobs))
.route(
"/app.bsky.ageassurance.getState",
get(api::age_assurance::get_state),
)
.route(
"/app.bsky.unspecced.getAgeAssuranceState",
get(api::age_assurance::get_age_assurance_state),
)
app_with_routes(state, ExternalRoutes::default())
}
pub fn app_with_routes(state: AppState, external: ExternalRoutes) -> Router {
let xrpc_router = external.xrpc
.fallback(async || (
StatusCode::NOT_IMPLEMENTED,
Json(json!({"error": "MethodNotImplemented", "message": "Method not implemented. For app.bsky.* methods, include an atproto-proxy header specifying your AppView."})),
@@ -558,120 +93,16 @@ pub fn app(state: AppState) -> Router {
.with_state(state.clone()),
);
let oauth_router = Router::new()
.route("/jwks", get(oauth::endpoints::oauth_jwks))
.route("/par", post(oauth::endpoints::pushed_authorization_request))
.route("/authorize", get(oauth::endpoints::authorize_get))
.route("/authorize", post(oauth::endpoints::authorize_post))
.route(
"/authorize/accounts",
get(oauth::endpoints::authorize_accounts),
)
.route(
"/authorize/select",
post(oauth::endpoints::authorize_select),
)
.route("/authorize/2fa", get(oauth::endpoints::authorize_2fa_get))
.route("/authorize/2fa", post(oauth::endpoints::authorize_2fa_post))
.route(
"/authorize/passkey",
get(oauth::endpoints::authorize_passkey_start),
)
.route(
"/authorize/passkey",
post(oauth::endpoints::authorize_passkey_finish),
)
.route(
"/passkey/check",
get(oauth::endpoints::check_user_has_passkeys),
)
.route(
"/security-status",
get(oauth::endpoints::check_user_security_status),
)
.route("/passkey/start", post(oauth::endpoints::passkey_start))
.route("/passkey/finish", post(oauth::endpoints::passkey_finish))
.route("/authorize/deny", post(oauth::endpoints::authorize_deny))
.route(
"/register/complete",
post(oauth::endpoints::register_complete),
)
.route(
"/establish-session",
post(oauth::endpoints::establish_session),
)
.route("/authorize/consent", get(oauth::endpoints::consent_get))
.route("/authorize/consent", post(oauth::endpoints::consent_post))
.route("/authorize/renew", post(oauth::endpoints::authorize_renew))
.route(
"/authorize/redirect",
get(oauth::endpoints::authorize_redirect),
)
.route("/delegation/auth", post(oauth::endpoints::delegation_auth))
.route(
"/delegation/auth-token",
post(oauth::endpoints::delegation_auth_token),
)
.route(
"/delegation/totp",
post(oauth::endpoints::delegation_totp_verify),
)
.route("/token", post(oauth::endpoints::token_endpoint))
.route("/revoke", post(oauth::endpoints::revoke_token))
.route("/introspect", post(oauth::endpoints::introspect_token))
.route("/sso/providers", get(sso::endpoints::get_sso_providers))
.route("/sso/initiate", post(sso::endpoints::sso_initiate))
.route(
"/sso/callback",
get(sso::endpoints::sso_callback).post(sso::endpoints::sso_callback_post),
)
.route("/sso/linked", get(sso::endpoints::get_linked_accounts))
.route("/sso/unlink", post(sso::endpoints::unlink_account))
.route(
"/sso/pending-registration",
get(sso::endpoints::get_pending_registration),
)
.route(
"/sso/complete-registration",
post(sso::endpoints::complete_registration),
)
.route(
"/sso/check-handle-available",
get(sso::endpoints::check_handle_available),
)
.layer(middleware::from_fn(oauth::verify::dpop_nonce_middleware));
let oauth_router = external.oauth;
let well_known_router = Router::new()
.route("/did.json", get(api::identity::well_known_did))
.route("/atproto-did", get(api::identity::well_known_atproto_did))
.route(
"/oauth-protected-resource",
get(oauth::endpoints::oauth_protected_resource),
)
.route(
"/oauth-authorization-server",
get(oauth::endpoints::oauth_authorization_server),
);
let well_known_router = external.well_known;
let router = Router::new()
.nest_service("/xrpc", xrpc_service)
.nest("/oauth", oauth_router)
.nest("/.well-known", well_known_router)
.route("/metrics", get(metrics::metrics_handler))
.route("/health", get(api::server::health))
.route("/robots.txt", get(api::server::robots_txt))
.route("/favicon.ico", get(api::server::get_logo))
.route("/u/{handle}/did.json", get(api::identity::user_did_doc))
.route(
"/webhook/telegram",
post(api::telegram_webhook::handle_telegram_webhook)
.layer(DefaultBodyLimit::max(64 * 1024)),
)
.route(
"/webhook/discord",
post(api::discord_webhook::handle_discord_webhook)
.layer(DefaultBodyLimit::max(64 * 1024)),
)
.merge(external.extra)
.layer(DefaultBodyLimit::max(
tranquil_config::get().server.max_blob_size as usize,
))
@@ -717,10 +148,6 @@ pub fn app(state: AppState) -> Router {
let serve_dir = ServeDir::new(frontend_dir).not_found_service(ServeFile::new(&index_path));
return router
.route(
"/oauth-client-metadata.json",
get(oauth::endpoints::frontend_client_metadata),
)
.route_service("/", ServeFile::new(&homepage_file))
.nest("/app", spa_router)
.fallback_service(serve_dir);
-2
View File
@@ -1,6 +1,4 @@
pub mod db;
pub mod endpoints;
pub mod jwks;
pub mod scopes;
pub mod verify;
-1
View File
@@ -1,5 +1,4 @@
pub mod config;
pub mod endpoints;
pub mod providers;
pub use config::SsoConfig;
+2 -2
View File
@@ -8,7 +8,7 @@ use crate::repo::PostgresBlockStore;
use crate::repo_write_lock::RepoWriteLocks;
use crate::sso::{SsoConfig, SsoManager};
use crate::storage::{BackupStorage, BlobStorage, create_backup_storage, create_blob_storage};
use crate::sync::firehose::SequencedEvent;
use tranquil_db_traits::SequencedEvent;
use sqlx::PgPool;
use std::error::Error;
use std::sync::Arc;
@@ -240,7 +240,7 @@ impl AppState {
.await,
) {
(true, Ok(Some(0))) => {
let code = crate::api::server::invite::gen_invite_code();
let code = crate::util::gen_invite_code();
tracing::info!(
"No users exist and invite codes are required. Bootstrap invite code: {}",
code
+4 -14
View File
@@ -1,24 +1,14 @@
pub mod blob;
pub mod car;
pub mod commit;
pub mod crawl;
pub mod deprecated;
pub mod firehose;
pub mod frame;
pub mod import;
pub mod listener;
pub mod repo;
pub mod subscribe_repos;
pub mod util;
pub mod verify;
pub use blob::{get_blob, list_blobs};
pub use commit::{get_latest_commit, get_repo_status, list_repos};
pub use crawl::{notify_of_update, request_crawl};
pub use deprecated::{get_checkout, get_head};
pub use repo::{get_blocks, get_record, get_repo};
pub use subscribe_repos::subscribe_repos;
pub use tranquil_db_traits::AccountStatus;
#[cfg(test)]
mod verify_tests;
pub use firehose::SequencedEvent;
pub use util::{
RepoAccessLevel, RepoAccount, RepoAvailabilityError, assert_repo_availability,
get_account_with_status,
+2 -2
View File
@@ -130,7 +130,7 @@ impl CarVerifier {
.ok_or(VerifyError::NoSigningKey)
}
async fn resolve_did_document(&self, did: &Did) -> Result<DidDocument<'static>, VerifyError> {
pub(crate) async fn resolve_did_document(&self, did: &Did) -> Result<DidDocument<'static>, VerifyError> {
let did_str = did.as_str();
if did_str.starts_with("did:plc:") {
self.resolve_plc_did(did_str).await
@@ -197,7 +197,7 @@ impl CarVerifier {
Ok(doc.into_static())
}
fn verify_mst_structure(
pub(crate) fn verify_mst_structure(
&self,
data_cid: &Cid,
blocks: &HashMap<Cid, Bytes>,
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::sync::verify::{CarVerifier, VerifyError};
use super::{CarVerifier, VerifyError};
use bytes::Bytes;
use cid::Cid;
use sha2::{Digest, Sha256};
+21
View File
@@ -535,3 +535,24 @@ mod tests {
);
}
}
pub(crate) fn gen_invite_random_token() -> String {
let mut rng = rand::thread_rng();
let chars: Vec<char> = BASE32_ALPHABET.chars().collect();
let gen_segment = |rng: &mut rand::rngs::ThreadRng, len: usize| -> String {
(0..len)
.map(|_| chars[rng.gen_range(0..chars.len())])
.collect()
};
format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5))
}
pub fn gen_invite_code() -> String {
let hostname = &tranquil_config::get().server.hostname;
let hostname_prefix = hostname.replace('.', "-");
format!("{}-{}", hostname_prefix, gen_invite_random_token())
}
pub fn is_self_hosted_did_web_enabled() -> bool {
tranquil_config::get().server.enable_pds_hosted_did_web
}
+1 -1
View File
@@ -96,7 +96,7 @@ fn test_unsigned_commit_has_5_fields() {
#[test]
fn test_create_signed_commit_helper() {
use tranquil_pds::api::repo::record::utils::create_signed_commit;
use tranquil_pds::repo_ops::create_signed_commit;
let signing_key = SigningKey::random(&mut rand::thread_rng());
let did: Did = "did:plc:testuser123456789abcdef"
+13 -2
View File
@@ -563,8 +563,19 @@ async fn spawn_server(config: ServerConfig) -> ServerInstance {
if let Some((cache, distributed_rate_limiter)) = config.cache {
state = state.with_cache(cache, distributed_rate_limiter);
}
tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await;
let app = tranquil_pds::app(state);
tranquil_sync::listener::start_sequencer_listener(state.clone()).await;
let app = tranquil_pds::app_with_routes(
state,
tranquil_pds::ExternalRoutes {
xrpc: tranquil_api::api_routes().merge(tranquil_sync::sync_routes()),
oauth: tranquil_oauth_server::oauth_routes(),
well_known: tranquil_oauth_server::well_known_oauth_routes()
.merge(tranquil_api::well_known_api_routes()),
extra: tranquil_api::misc_routes()
.merge(tranquil_api::webhook_routes())
.merge(tranquil_oauth_server::frontend_client_metadata_route()),
},
);
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
});
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "tranquil-server"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
tranquil-pds = { workspace = true }
tranquil-sync = { workspace = true }
tranquil-api = { workspace = true }
tranquil-oauth-server = { workspace = true }
tranquil-config = { workspace = true }
axum = { workspace = true }
clap = { workspace = true }
dotenvy = { workspace = true }
ed25519-dalek = { workspace = true }
hex = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
[features]
default = ["frontend", "s3", "valkey"]
frontend = ["tranquil-pds/frontend"]
s3 = ["tranquil-pds/s3"]
valkey = ["tranquil-pds/valkey"]
+12
View File
@@ -0,0 +1,12 @@
use std::process::Command;
fn main() {
let timestamp = Command::new("date")
.arg("+%Y-%m-%d %H:%M:%S UTC")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|_| "unknown".to_string());
println!("cargo:rustc-env=BUILD_TIMESTAMP={}", timestamp);
println!("cargo:rerun-if-changed=build.rs");
}

Some files were not shown because too many files have changed in this diff Show More