Magazi is a content distribution platform that gates access to files using ATProtocol (Bluesky) identity and cryptographic proofs. download.ngerakines.me/
atprotocol appview atprotocol-attestations
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 103 lines 3.4 kB view raw
1mod error; 2mod handlers; 3mod oauth_storage; 4mod resolvers; 5mod session; 6mod state; 7mod templates; 8 9use std::net::SocketAddr; 10use std::sync::Arc; 11 12use axum::{ 13 Router, 14 http::{HeaderValue, header}, 15 routing::{get, post}, 16}; 17use tower_http::{ 18 limit::RequestBodyLimitLayer, set_header::SetResponseHeaderLayer, trace::TraceLayer, 19}; 20use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 21 22use magazi::config::Config; 23use state::{AppState, InnerAppState}; 24 25#[tokio::main] 26async fn main() -> anyhow::Result<()> { 27 tracing_subscriber::registry() 28 .with( 29 tracing_subscriber::EnvFilter::try_from_default_env() 30 .unwrap_or_else(|_| "magazi=warn".into()), 31 ) 32 .with(tracing_subscriber::fmt::layer()) 33 .init(); 34 35 let config = Config::from_env()?; 36 37 // Verify all catalog files exist 38 for entry in &config.catalog { 39 let file_path = config.files_path.join(&entry.id); 40 if !file_path.exists() { 41 panic!( 42 "Catalog entry '{}' references missing file: {}", 43 entry.name, 44 file_path.display() 45 ); 46 } 47 } 48 49 let http_port = config.http_port; 50 let inner_state = InnerAppState::new(config).await?; 51 let state = AppState(Arc::new(inner_state)); 52 53 // Warm up the entitlement cache for anonymous visitors 54 handlers::util_entitlements::warmup_anonymous_cache(&state).await; 55 56 let app = Router::new() 57 .route("/", get(handlers::handler_home::home)) 58 .route("/robots.txt", get(handlers::handler_robots::robots_txt)) 59 .route( 60 "/oauth-client-metadata.json", 61 get(handlers::handler_oauth::client_metadata), 62 ) 63 .route("/login", get(handlers::handler_auth::login_form)) 64 .route("/login", post(handlers::handler_auth::login_start)) 65 .route("/login/callback", get(handlers::handler_auth::login_callback)) 66 .route("/download/{id}", get(handlers::handler_download::download_file)) 67 .route( 68 "/xrpc/com.atproto.sync.getBlob", 69 get(handlers::handler_getblob::get_blob), 70 ) 71 .route("/logout", get(handlers::handler_auth::logout)) 72 // Security headers 73 .layer(SetResponseHeaderLayer::overriding( 74 header::X_CONTENT_TYPE_OPTIONS, 75 HeaderValue::from_static("nosniff"), 76 )) 77 .layer(SetResponseHeaderLayer::overriding( 78 header::X_FRAME_OPTIONS, 79 HeaderValue::from_static("DENY"), 80 )) 81 .layer(SetResponseHeaderLayer::overriding( 82 header::CONTENT_SECURITY_POLICY, 83 HeaderValue::from_static( 84 "default-src 'self'; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src https://cdnjs.cloudflare.com", 85 ), 86 )) 87 .layer(SetResponseHeaderLayer::overriding( 88 header::STRICT_TRANSPORT_SECURITY, 89 HeaderValue::from_static("max-age=31536000; includeSubDomains"), 90 )) 91 // Request body size limit (1KB for form submissions) 92 .layer(RequestBodyLimitLayer::new(1024)) 93 .layer(TraceLayer::new_for_http()) 94 .with_state(state); 95 96 let addr = SocketAddr::from(([0, 0, 0, 0], http_port)); 97 tracing::info!("listening on {}", addr); 98 99 let listener = tokio::net::TcpListener::bind(addr).await?; 100 axum::serve(listener, app).await?; 101 102 Ok(()) 103}