1use async_trait::async_trait;
2use axum::Router;
3use axum::body::Body;
4use axum::extract::{Request, State};
5use axum::http::header::{ALLOW, CACHE_CONTROL, CONTENT_TYPE, HOST};
6use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
7use axum::middleware::{self, Next};
8use axum::response::Response;
9use axum::routing::MethodRouter;
10use kcode_k1_http_replay::{ReplayError, ReplayWindow};
11use kcode_k1_http_request::{Envelope, VerifyError, registration_public_key};
12use std::fmt::{self, Display, Formatter};
13use std::sync::Arc;
14use tower_http::cors::{Any, CorsLayer};
15
16pub use kcode_k1_http_request::CanonicalUsername;
17
18pub struct Config {
19 pub server_id: String,
20 pub public_origin: String,
21 pub max_body_bytes: usize,
22}
23
24#[derive(Clone, Copy, Eq, PartialEq)]
25pub struct Identity {
26 user_id: [u8; 12],
27 public_key: [u8; 32],
28}
29
30impl Identity {
31 pub fn new(user_id: [u8; 12], public_key: [u8; 32]) -> Self {
32 Self {
33 user_id,
34 public_key,
35 }
36 }
37
38 pub fn user_id(&self) -> &[u8; 12] {
39 &self.user_id
40 }
41
42 pub fn public_key(&self) -> &[u8; 32] {
43 &self.public_key
44 }
45}
46
47#[derive(Clone)]
48pub struct Principal {
49 user_id: [u8; 12],
50 username: CanonicalUsername,
51}
52
53impl Principal {
54 pub fn user_id(&self) -> &[u8; 12] {
55 &self.user_id
56 }
57
58 pub fn username(&self) -> &CanonicalUsername {
59 &self.username
60 }
61}
62
63#[derive(Clone)]
64pub struct RegistrationPrincipal {
65 username: CanonicalUsername,
66 public_key: [u8; 32],
67}
68
69impl RegistrationPrincipal {
70 pub fn username(&self) -> &CanonicalUsername {
71 &self.username
72 }
73
74 pub fn public_key(&self) -> &[u8; 32] {
75 &self.public_key
76 }
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub enum IdentityError {
81 Unavailable,
82}
83
84impl Display for IdentityError {
85 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
86 formatter.write_str("identity provider unavailable")
87 }
88}
89
90impl std::error::Error for IdentityError {}
91
92#[async_trait]
93pub trait IdentityProvider: Send + Sync + 'static {
94 async fn lookup(&self, username: &CanonicalUsername)
95 -> Result<Option<Identity>, IdentityError>;
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub enum ConfigError {
100 EmptyServerId,
101 InvalidPublicOrigin,
102 InvalidBodyLimit,
103}
104
105impl Display for ConfigError {
106 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
107 formatter.write_str(match self {
108 Self::EmptyServerId => "server ID is empty",
109 Self::InvalidPublicOrigin => "public origin is invalid",
110 Self::InvalidBodyLimit => "body limit is invalid",
111 })
112 }
113}
114
115impl std::error::Error for ConfigError {}
116
117pub struct K1Http {
118 state: Arc<AppState>,
119}
120
121struct AppState {
122 server_id: String,
123 public_origin: String,
124 authority: String,
125 max_body_bytes: usize,
126 replay: ReplayWindow,
127 identities: Arc<dyn IdentityProvider>,
128}
129
130impl K1Http {
131 pub fn new(
132 config: Config,
133 replay: kcode_k1_http_replay::ReplayWindow,
134 identities: Arc<dyn IdentityProvider>,
135 ) -> Result<Self, ConfigError> {
136 if config.server_id.is_empty() {
137 return Err(ConfigError::EmptyServerId);
138 }
139 if config.max_body_bytes == 0 {
140 return Err(ConfigError::InvalidBodyLimit);
141 }
142 let authority =
143 origin_authority(&config.public_origin).ok_or(ConfigError::InvalidPublicOrigin)?;
144 Ok(Self {
145 state: Arc::new(AppState {
146 server_id: config.server_id,
147 public_origin: config.public_origin,
148 authority,
149 max_body_bytes: config.max_body_bytes,
150 replay,
151 identities,
152 }),
153 })
154 }
155
156 pub fn router(
157 &self,
158 registration: MethodRouter,
159 terms: MethodRouter,
160 authenticated: Router,
161 ) -> Router {
162 let authenticated = authenticated.layer(middleware::from_fn_with_state(
163 self.state.clone(),
164 authenticate,
165 ));
166 let registration = registration.layer(middleware::from_fn_with_state(
167 self.state.clone(),
168 authenticate_registration,
169 ));
170 Router::new()
171 .nest("/api", authenticated)
172 .route("/api/register", registration)
173 .route("/api/terms", terms)
174 .layer(middleware::from_fn(terms_method_guard))
175 .layer(cors())
176 .layer(middleware::from_fn_with_state(self.state.clone(), api_gate))
177 }
178}
179
180fn origin_authority(value: &str) -> Option<String> {
181 let uri: Uri = value.parse().ok()?;
182 let scheme = uri.scheme_str()?;
183 if !scheme.eq_ignore_ascii_case("http") && !scheme.eq_ignore_ascii_case("https") {
184 return None;
185 }
186 let authority = uri.authority()?.as_str();
187 if authority.is_empty() || authority.contains('@') || uri.query().is_some() {
188 return None;
189 }
190 if !matches!(uri.path(), "" | "/") {
191 return None;
192 }
193 Some(authority.to_owned())
194}
195
196fn cors() -> CorsLayer {
197 CorsLayer::new()
198 .allow_origin(Any)
199 .allow_methods([
200 Method::GET,
201 Method::POST,
202 Method::PUT,
203 Method::PATCH,
204 Method::DELETE,
205 Method::OPTIONS,
206 Method::HEAD,
207 ])
208 .allow_headers([
209 CONTENT_TYPE,
210 header("k1-username"),
211 header("k1-epoch"),
212 header("k1-nonce"),
213 header("k1-body-sha256"),
214 header("k1-signature"),
215 header("k1-public-key"),
216 ])
217 .expose_headers([header("k1-epoch")])
218}
219
220fn header(value: &'static str) -> HeaderName {
221 HeaderName::from_static(value)
222}
223
224fn set(headers: &mut HeaderMap, name: HeaderName, value: &'static str) {
225 headers.insert(name, HeaderValue::from_static(value));
226}
227
228async fn api_gate(State(state): State<Arc<AppState>>, request: Request, next: Next) -> Response {
229 let authority_ok = single(request.headers(), HOST.as_str())
230 .is_ok_and(|value| value.as_bytes() == state.authority.as_bytes());
231 if !authority_ok {
232 return finish(
233 &state,
234 error(StatusCode::MISDIRECTED_REQUEST, "invalid_request_authority"),
235 )
236 .await;
237 }
238 if state.replay.current_epoch().await.is_err() {
239 return decorate(
240 error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
241 None,
242 );
243 }
244 finish(&state, next.run(request).await).await
245}
246
247async fn terms_method_guard(request: Request, next: Next) -> Response {
248 if request.uri().path() == "/api/terms"
249 && request.method() != Method::GET
250 && request.method() != Method::HEAD
251 {
252 return method_not_allowed("GET, HEAD");
253 }
254 next.run(request).await
255}
256
257async fn finish(state: &AppState, response: Response) -> Response {
258 match state.replay.current_epoch().await {
259 Ok(epoch) => decorate(response, Some(epoch)),
260 Err(_) => decorate(
261 error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
262 None,
263 ),
264 }
265}
266
267fn decorate(mut response: Response, epoch: Option<u64>) -> Response {
268 let headers = response.headers_mut();
269 set(headers, CACHE_CONTROL, "no-store");
270 set(headers, header("x-content-type-options"), "nosniff");
271 set(headers, header("access-control-allow-origin"), "*");
272 set(headers, header("access-control-expose-headers"), "k1-epoch");
273 if let Some(epoch) = epoch
274 && let Ok(value) = HeaderValue::from_str(&epoch.to_string())
275 {
276 headers.insert(header("k1-epoch"), value);
277 }
278 response
279}
280
281fn single<'a>(headers: &'a HeaderMap, name: &str) -> Result<&'a HeaderValue, ()> {
282 let mut values = headers.get_all(name).iter();
283 let value = values.next().ok_or(())?;
284 if values.next().is_some() {
285 return Err(());
286 }
287 Ok(value)
288}
289
290async fn authenticate(
291 State(state): State<Arc<AppState>>,
292 request: Request,
293 next: Next,
294) -> Response {
295 proceed(authenticated_request(&state, request).await, next).await
296}
297
298async fn authenticate_registration(
299 State(state): State<Arc<AppState>>,
300 request: Request,
301 next: Next,
302) -> Response {
303 if request.method() != Method::POST {
304 return method_not_allowed("POST");
305 }
306 proceed(registration_request(&state, request).await, next).await
307}
308
309async fn proceed(result: Result<Request, Response>, next: Next) -> Response {
310 match result {
311 Ok(request) => next.run(request).await,
312 Err(response) => response,
313 }
314}
315
316async fn authenticated_request(state: &AppState, request: Request) -> Result<Request, Response> {
317 let envelope = Envelope::parse(request.headers())
318 .map_err(|_| error(StatusCode::BAD_REQUEST, "malformed_authentication_envelope"))?;
319 validate_epoch(state, envelope.epoch()).await?;
320 let identity = match state.identities.lookup(envelope.username()).await {
321 Ok(Some(identity)) => identity,
322 Ok(None) => return Err(error(StatusCode::UNAUTHORIZED, "authentication_failed")),
323 Err(IdentityError::Unavailable) => {
324 return Err(error(
325 StatusCode::SERVICE_UNAVAILABLE,
326 "identity_provider_unavailable",
327 ));
328 }
329 };
330 let mut request = verified(state, &envelope, request, identity.public_key()).await?;
331 let mut replay_id = [0_u8; 32];
332 replay_id[..12].copy_from_slice(identity.user_id());
333 state
334 .replay
335 .admit(&replay_id, envelope.epoch(), *envelope.nonce())
336 .await
337 .map_err(replay_error)?;
338 request.extensions_mut().insert(Principal {
339 user_id: *identity.user_id(),
340 username: envelope.username().clone(),
341 });
342 Ok(request)
343}
344
345async fn registration_request(state: &AppState, request: Request) -> Result<Request, Response> {
346 let envelope = Envelope::parse(request.headers())
347 .map_err(|_| error(StatusCode::BAD_REQUEST, "malformed_authentication_envelope"))?;
348 if single(request.headers(), "k1-public-key").is_err() {
349 return Err(error(
350 StatusCode::BAD_REQUEST,
351 "malformed_authentication_envelope",
352 ));
353 }
354 let candidate = registration_public_key(request.headers())
355 .map_err(|_| error(StatusCode::UNAUTHORIZED, "authentication_failed"))?;
356 validate_epoch(state, envelope.epoch()).await?;
357 let mut request = verified(state, &envelope, request, &candidate).await?;
358 request.extensions_mut().insert(RegistrationPrincipal {
359 username: envelope.username().clone(),
360 public_key: candidate,
361 });
362 Ok(request)
363}
364
365async fn verified(
366 state: &AppState,
367 envelope: &Envelope,
368 request: Request,
369 public_key: &[u8; 32],
370) -> Result<Request, Response> {
371 envelope
372 .verify(
373 request,
374 &state.server_id,
375 &state.public_origin,
376 public_key,
377 state.max_body_bytes,
378 )
379 .await
380 .map_err(verify_error)
381}
382
383async fn validate_epoch(state: &AppState, epoch: u64) -> Result<(), Response> {
384 state
385 .replay
386 .validate_epoch(epoch)
387 .await
388 .map(|_| ())
389 .map_err(replay_error)
390}
391
392fn replay_error(cause: ReplayError) -> Response {
393 match cause {
394 ReplayError::EpochOutsideWindow { .. } => error(StatusCode::UNAUTHORIZED, "stale_epoch"),
395 ReplayError::Replay { .. } => error(StatusCode::CONFLICT, "replay"),
396 ReplayError::CapacityExceeded { .. } => {
397 error(StatusCode::TOO_MANY_REQUESTS, "nonce_capacity")
398 }
399 _ => error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
400 }
401}
402
403fn verify_error(cause: VerifyError) -> Response {
404 match cause {
405 VerifyError::AuthenticationFailed => {
406 error(StatusCode::UNAUTHORIZED, "authentication_failed")
407 }
408 VerifyError::BodyDigestMismatch => error(StatusCode::BAD_REQUEST, "body_digest_mismatch"),
409 VerifyError::BodyTooLarge => error(StatusCode::PAYLOAD_TOO_LARGE, "body_too_large"),
410 }
411}
412
413fn method_not_allowed(allow: &'static str) -> Response {
414 let mut response = Response::new(Body::empty());
415 *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
416 set(response.headers_mut(), ALLOW, allow);
417 response
418}
419
420fn error(status: StatusCode, code: &'static str) -> Response {
421 let mut response = Response::new(Body::from(format!("{{\"error\":\"{code}\"}}")));
422 *response.status_mut() = status;
423 set(response.headers_mut(), CONTENT_TYPE, "application/json");
424 response
425}