codoseo_web/agent/
auth.rs1use axum::extract::FromRequestParts;
6use axum::http::HeaderMap;
7use axum::http::header::AUTHORIZATION;
8use axum::http::request::Parts;
9use codoseo_store::accounts::Account;
10use uuid::Uuid;
11
12use super::error::AgentError;
13use super::keys;
14use crate::metrics::{self, Surface, Tier};
15use crate::state::AppState;
16
17#[derive(Debug, Clone)]
19pub struct ApiCaller {
20 pub account: Account,
21 pub key_id: Uuid,
22}
23
24const NO_KEY: &str = "Missing API key. Send it as the header \"Authorization: Bearer <key>\".";
25const BAD_KEY: &str = "That API key is not valid, or it was revoked.";
27
28pub fn bearer_key(headers: &HeaderMap) -> Result<Option<&str>, AgentError> {
31 let Some(value) = headers.get(AUTHORIZATION) else {
32 return Ok(None);
33 };
34 let bad = || AgentError::Unauthorized(BAD_KEY.to_owned());
35 let value = value.to_str().map_err(|_| bad())?;
36 let (scheme, key) = value.split_once(' ').ok_or_else(bad)?;
37 if !scheme.eq_ignore_ascii_case("bearer") {
38 return Err(AgentError::Unauthorized(
39 "Use the header \"Authorization: Bearer <key>\".".to_owned(),
40 ));
41 }
42 let key = key.trim();
43 if keys::is_well_formed(key) {
44 Ok(Some(key))
45 } else {
46 Err(bad())
47 }
48}
49
50pub async fn resolve_key(state: &AppState, key: &str) -> Result<ApiCaller, AgentError> {
52 match codoseo_store::api_keys::authenticate(&state.pool, &keys::hash_key(key)).await? {
53 Some((key_id, account)) => Ok(ApiCaller { account, key_id }),
54 None => Err(AgentError::Unauthorized(BAD_KEY.to_owned())),
55 }
56}
57
58pub async fn authenticate(state: &AppState, headers: &HeaderMap) -> Result<ApiCaller, AgentError> {
60 match bearer_key(headers)? {
61 Some(key) => resolve_key(state, key).await,
62 None => Err(AgentError::Unauthorized(NO_KEY.to_owned())),
63 }
64}
65
66impl FromRequestParts<AppState> for ApiCaller {
68 type Rejection = AgentError;
69
70 async fn from_request_parts(
71 parts: &mut Parts,
72 state: &AppState,
73 ) -> Result<ApiCaller, AgentError> {
74 let caller = authenticate(state, &parts.headers).await;
75 if let Err(e) = &caller {
77 metrics::api_request(Surface::Rest, Tier::Key, e.code());
78 }
79 caller
80 }
81}