// The non-OAuth2 security schemes' runtime extractors (← FastAPI's `HTTPBasic` and
// `APIKeyHeader` / `APIKeyQuery` / `APIKeyCookie`). `security_scheme.mbt` already describes these to
// clients under `securitySchemes`; this is the enforcement side — pulling the credential off the
// request so a route can authenticate against it, the same way `Context::bearer_token` does for
// OAuth2.
///|
/// The credentials carried in an HTTP Basic `Authorization` header (← FastAPI's
/// `HTTPBasicCredentials`): the username and password from `base64(username:password)`.
pub(all) struct HttpBasicCredentials {
username : String
password : String
}
///|
/// The index of the first `:` in `s`, or `-1`.
fn first_colon(s : String) -> Int {
for i = 0; i < s.length(); i = i + 1 {
if s[i] == ':' {
return i
}
}
-1
}
///|
/// Parse an HTTP Basic `Authorization` header value into credentials (← FastAPI's `HTTPBasic`),
/// or `None`. Matches the `Basic` scheme case-insensitively (RFC 7617), base64-decodes the rest, and
/// splits on the first colon so a password may itself contain colons.
pub fn parse_basic_auth(header : String) -> HttpBasicCredentials? {
let trimmed = trim_spaces(header)
let prefix = "basic "
guard trimmed.length() >= prefix.length() else { return None }
guard trimmed[0:prefix.length()].to_owned().to_lower() == prefix else {
return None
}
let encoded = trim_spaces(trimmed[prefix.length():].to_owned())
let decoded = @utf8.decode_lossy(@base64.decode_lossy(encoded)[:])
let colon = first_colon(decoded)
guard colon >= 0 else { return None }
Some({
username: decoded[0:colon].to_owned(),
password: decoded[colon + 1:].to_owned(),
})
}
///|
/// The HTTP Basic credentials on this request (← FastAPI's `HTTPBasic` dependency), or `None`.
pub fn Context::http_basic(self : Context) -> HttpBasicCredentials? {
match self.request.header("authorization") {
Some(h) => parse_basic_auth(h)
None => None
}
}
///|
/// The API key carried in the request header `name` (← FastAPI's `APIKeyHeader`), or `None`. Header
/// names are matched against the request's lower-cased headers.
pub fn Context::api_key_header(self : Context, name : String) -> String? {
self.request.header(name.to_lower())
}
///|
/// The API key carried in the query parameter `name` (← FastAPI's `APIKeyQuery`), or `None`.
pub fn Context::api_key_query(self : Context, name : String) -> String? {
self.query(name)
}
///|
/// The API key carried in the cookie `name` (← FastAPI's `APIKeyCookie`), or `None`.
pub fn Context::api_key_cookie(self : Context, name : String) -> String? {
self.cookie(name)
}