Server overhaul: shared helpers, dead code, synchronous tasks::start, stale comments
crate::lock replaces three poison-tolerant lock helpers; the rate limiter reuses auth::bearer_token; the landing page reuses invites::display_code. Removed: AppError::not_implemented (D-004 placeholder, only its own test used it) and the unused Mutation::mutation_id/entity_type. tasks::start never awaited or failed: now a plain fn returning AppState. Milestone-ownership comments (M2a/M3/M4) rewritten to say what the code does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -12,7 +12,7 @@ use axum::response::{IntoResponse, Response};
|
||||
use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, utf8_percent_encode};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::api::invites::FROM_PARAM;
|
||||
use crate::api::invites::{FROM_PARAM, display_code};
|
||||
use crate::api::{apk, validate};
|
||||
|
||||
/// Longest code the page accepts (E-4).
|
||||
@@ -110,15 +110,6 @@ fn clean_code(code: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn grouped(code: &str) -> String {
|
||||
let chars: Vec<char> = code.chars().collect();
|
||||
chars
|
||||
.chunks(4)
|
||||
.map(|c| c.iter().collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join("-")
|
||||
}
|
||||
|
||||
const STYLE: &str = "body{font-family:system-ui,sans-serif;max-width:28rem;margin:3rem auto;padding:0 1rem;\
|
||||
text-align:center;color:#1b1b1b;background:#fff}h1{font-size:2rem}.code{font-family:monospace;font-size:1.5rem;\
|
||||
letter-spacing:.1em;margin:1.5rem 0}a.button{display:block;margin:.75rem 0;padding:.9rem;border-radius:.75rem;\
|
||||
@@ -184,7 +175,7 @@ pub fn render(
|
||||
lang = copy.html_lang,
|
||||
app = copy.app_name,
|
||||
instruction = copy.instruction,
|
||||
code = escape(&grouped(&code)),
|
||||
code = escape(&display_code(&code)),
|
||||
open = escape(&open_url),
|
||||
open_label = copy.open,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! HTTP handlers, one file per area (SPEC §14.7), and the extractors and response types they share.
|
||||
//!
|
||||
//! Every handler is implemented: `health` (E-1) and `setup` (E-2) by M2a, E-3 to E-30 and E-33 by M3, the sync
|
||||
//! handlers E-31 and E-32 (`sync.rs`, protocol in `crate::sync`) by M4.
|
||||
//! HTTP handlers, one file per area (SPEC §14.7), and the extractors and response types they share. The sync
|
||||
//! handlers E-31 and E-32 are in `sync.rs`, the protocol itself in `crate::sync`; `rows` and `validate` hold the
|
||||
//! row access and input rules the handlers, the sync protocol and the importer share.
|
||||
|
||||
pub mod accounts;
|
||||
pub mod apk;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -31,11 +31,11 @@ use axum::response::{IntoResponse, Response};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use tower::{Layer, Service};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::audit::BoxFuture;
|
||||
use crate::auth::{is_token_shaped, sha256_hex};
|
||||
use crate::auth::{bearer_token, is_token_shaped, sha256_hex};
|
||||
use crate::error::AppError;
|
||||
use crate::time::Clock;
|
||||
use crate::{AppState, lock};
|
||||
|
||||
/// Unauthenticated requests per window per client IP (SPEC §8.6).
|
||||
pub const UNAUTHENTICATED_LIMIT: u32 = 60;
|
||||
@@ -161,13 +161,6 @@ pub struct Limiter {
|
||||
buckets: Mutex<Buckets>,
|
||||
}
|
||||
|
||||
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
|
||||
match mutex.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => poisoned.into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Limiter {
|
||||
pub fn new(clock: Arc<dyn Clock>, trusted_proxy: String) -> Self {
|
||||
Self {
|
||||
@@ -230,15 +223,7 @@ impl Limiter {
|
||||
if PUBLIC_ROUTES.contains(&route) || route.is_empty() {
|
||||
return Key::Ip(ip);
|
||||
}
|
||||
let token = request
|
||||
.headers()
|
||||
.get(axum::http::header::AUTHORIZATION)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.split_once(' '))
|
||||
.filter(|(scheme, _)| scheme.eq_ignore_ascii_case("bearer"))
|
||||
.map(|(_, token)| token.trim())
|
||||
.filter(|token| is_token_shaped(token));
|
||||
match token {
|
||||
match bearer_token(request.headers()).filter(|token| is_token_shaped(token)) {
|
||||
Some(token) => Key::Token {
|
||||
ip,
|
||||
hash: sha256_hex(token),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Input validation shared by the M3 handlers (SPEC §8.6, §9.6, §9.7).
|
||||
//! Input validation shared by the REST handlers, the sync push checks and the importer (SPEC §8.6, §9.6, §9.7).
|
||||
//!
|
||||
//! Strings are trimmed of leading and trailing whitespace and their length is counted in Unicode code points after
|
||||
//! trimming; the trimmed value is what the server stores.
|
||||
|
||||
Reference in New Issue
Block a user