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 pub scopes: Vec<String>,
21}
22
23pub(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}