Skip to main content

sofie_http/
bearer.rs

1use http::header::{AUTHORIZATION, WWW_AUTHENTICATE};
2use http::{HeaderMap, HeaderValue, StatusCode};
3use sofie_auth::BearerToken;
4use sofie_core::{Timestamp, Uuid};
5
6use crate::admission::too_many_requests;
7use crate::answer::{self, Response};
8use crate::pipeline::{Asked, Pipeline};
9use crate::session::RequestSession;
10use crate::target::Target;
11use crate::throttle::{Limiter, Rate, Requester};
12
13const REFUSED: &str = "The bearer token is unknown, expired or revoked.";
14const FAILURES: Rate = Rate::per_minute(10);
15
16pub struct Bearer {
17    pub token: Uuid,
18    pub principal: Uuid,
19    /// Permission patterns narrowing what the principal holds in a `Permitted` area.
20    pub scopes: Vec<String>,
21}
22
23/// Failed lookups per caller; past them the caller is throttled, which the shield counts.
24pub(crate) struct Tokens {
25    failures: Limiter,
26}
27
28impl Default for Tokens {
29    fn default() -> Self {
30        Self {
31            failures: Limiter::new(FAILURES),
32        }
33    }
34}
35
36pub(crate) async fn open(
37    pipeline: &Pipeline,
38    asked: Asked<'_>,
39    target: &Target,
40    now: Timestamp,
41) -> Result<RequestSession, Box<Response>> {
42    let headers = &asked.request.headers;
43    let Some(text) = presented(target, headers) else {
44        return Ok(pipeline.sessions.open(headers, now));
45    };
46    #[cfg(feature = "shield")]
47    if let Some(refusal) = pipeline.shield.refusal(asked, None) {
48        return Err(Box::new(refusal));
49    }
50    let caller = Requester::of(None, asked.client);
51    if let Err(wait) = pipeline.tokens.failures.check(caller, now) {
52        return Err(Box::new(too_many_requests(wait, asked)));
53    }
54    let area = target.audience.name;
55    let members = pipeline.sessions.members.as_deref();
56    let found =
57        match (BearerToken::presented(text), members) {
58            (Some(token), Some(members)) => members
59                .bearer(&pipeline.db, area, &token.digest())
60                .await
61                .map_err(|error| Box::new(answer::failure(error, asked.caller)))?,
62            _ => None,
63        };
64    let Some(bearer) = found else {
65        return Err(Box::new(refused(&pipeline.tokens, asked, area, now)));
66    };
67    tracing::debug!(token.id = %bearer.token, area, "a bearer token opened the request");
68    Ok(RequestSession::bearer(area, bearer, now))
69}
70
71fn presented<'h>(target: &Target, headers: &'h HeaderMap) -> Option<&'h str> {
72    if !target.audience.tokens {
73        return None;
74    }
75    crate::caller::bearer_credentials(headers.get(AUTHORIZATION)?)
76}
77
78fn refused(tokens: &Tokens, asked: Asked<'_>, area: &str, now: Timestamp) -> Response {
79    if let Err(wait) = tokens
80        .failures
81        .admit(Requester::of(None, asked.client), now)
82    {
83        return too_many_requests(wait, asked);
84    }
85    tracing::warn!(area, "refused an unknown, expired or revoked bearer token");
86    let mut response = answer::refusal(StatusCode::UNAUTHORIZED, REFUSED, asked.caller);
87    response.headers_mut().insert(
88        WWW_AUTHENTICATE,
89        HeaderValue::from_static("Bearer error=\"invalid_token\""),
90    );
91    response
92}