1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! HTTP server for the [Adler](https://github.com/commit3296/adler)
//! OSINT username-search engine.
//!
//! This crate hosts the JSON API and embedded `SolidJS` web UI for
//! Adler. It is a thin shell around [`adler_core`]: scans run through
//! the same [`adler_core::executor`] the CLI uses, and the same
//! [`adler_core::Client`] is shared across all in-process scans.
//!
//! ## Quick start
//!
//! ```no_run
//! use std::net::SocketAddr;
//! use adler_core::{Client, Registry};
//! use adler_server::{AppConfig, serve};
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let registry = Registry::default_embedded()?;
//! // Use the caller's filtering rules — the CLI already exposes
//! // --only/--tag/--exclude, so the server just runs whatever site
//! // list it's handed.
//! let filter = adler_core::SiteFilter::default();
//! let sites = registry.filter_with(&filter);
//! let catalog = registry.matches_with(&filter);
//! let client = Client::builder().build()?;
//! let config = AppConfig {
//! bind: "127.0.0.1:8765".parse::<SocketAddr>()?,
//! scan_capacity: 32,
//! scans_dir: None, // or Some(adler_server::default_scans_dir())
//! };
//! serve(sites, catalog, client, config).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Routes
//!
//! | Route | Method | Purpose |
//! |------------------------------------|--------|--------------------------------------|
//! | `/api/health` | GET | liveness |
//! | `/api/sites` | GET | site catalogue |
//! | `/api/scan` | POST | start a scan, returns a `scan_id` |
//! | `/api/scan/{id}` | GET | poll status / final aggregate |
//! | `/api/scan/{id}/report` | GET | investigation report export |
//! | `/api/scan/{id}/stream` | GET | Server-Sent Events |
//! | `/api/scan/{id}/retry` | POST | retry one site in a scan |
//! | `/api/scan/{id}/refilter` | POST | cancel and restart with new filters |
//! | `/api/scans` | GET | recent scan history |
//! | `/api/access` | GET | read-only access-engine summary |
//! | `/` | GET | embedded `SolidJS` SPA |
//!
//! ## Threading and shutdown
//!
//! [`serve`] binds the TCP listener, installs a `SIGINT` / `SIGTERM`
//! graceful-shutdown signal, and runs until the listener closes. All
//! state (registry, client, in-flight scans) lives in an [`AppState`]
//! cloned into each handler — no global mutables.
use SocketAddr;
use PathBuf;
use ;
use TcpListener;
use signal;
pub use router;
pub use ;
pub use ;
pub use ;
pub use AppState;
/// Server configuration.
///
/// `bind` is the TCP socket the server listens on; defaults are
/// imposed by the caller (the CLI binds `127.0.0.1:8765` and refuses
/// to bind a non-loopback address unless explicitly told to — there
/// is no authentication on the API).
/// Run the server until the listener closes or a shutdown signal arrives.
///
/// `sites` is the pre-filtered enabled site list every scan dispatched
/// through this server runs against. `catalog` is the same startup filter
/// including disabled/parked entries so API/UI surfaces can explain why a
/// site is unavailable. `client` is the pre-built HTTP client (so
/// configuration like proxy, throttle, and browser backend flows from the
/// CLI flags through here unchanged).
pub async
async