|
| 1 | +// The non-OAuth2 security schemes' runtime extractors (← FastAPI's `HTTPBasic` and |
| 2 | +// `APIKeyHeader` / `APIKeyQuery` / `APIKeyCookie`). `security_scheme.mbt` already describes these to |
| 3 | +// clients under `securitySchemes`; this is the enforcement side — pulling the credential off the |
| 4 | +// request so a route can authenticate against it, the same way `Context::bearer_token` does for |
| 5 | +// OAuth2. |
| 6 | + |
| 7 | +///| |
| 8 | +/// The credentials carried in an HTTP Basic `Authorization` header (← FastAPI's |
| 9 | +/// `HTTPBasicCredentials`): the username and password from `base64(username:password)`. |
| 10 | +pub(all) struct HttpBasicCredentials { |
| 11 | + username : String |
| 12 | + password : String |
| 13 | +} |
| 14 | + |
| 15 | +///| |
| 16 | +/// The index of the first `:` in `s`, or `-1`. |
| 17 | +fn first_colon(s : String) -> Int { |
| 18 | + for i = 0; i < s.length(); i = i + 1 { |
| 19 | + if s[i] == ':' { |
| 20 | + return i |
| 21 | + } |
| 22 | + } |
| 23 | + -1 |
| 24 | +} |
| 25 | + |
| 26 | +///| |
| 27 | +/// Parse an HTTP Basic `Authorization` header value into credentials (← FastAPI's `HTTPBasic`), |
| 28 | +/// or `None`. Matches the `Basic` scheme case-insensitively (RFC 7617), base64-decodes the rest, and |
| 29 | +/// splits on the first colon so a password may itself contain colons. |
| 30 | +pub fn parse_basic_auth(header : String) -> HttpBasicCredentials? { |
| 31 | + let trimmed = trim_spaces(header) |
| 32 | + let prefix = "basic " |
| 33 | + guard trimmed.length() >= prefix.length() else { return None } |
| 34 | + guard trimmed[0:prefix.length()].to_owned().to_lower() == prefix else { |
| 35 | + return None |
| 36 | + } |
| 37 | + let encoded = trim_spaces(trimmed[prefix.length():].to_owned()) |
| 38 | + let decoded = @utf8.decode_lossy(@base64.decode_lossy(encoded)[:]) |
| 39 | + let colon = first_colon(decoded) |
| 40 | + guard colon >= 0 else { return None } |
| 41 | + Some({ |
| 42 | + username: decoded[0:colon].to_owned(), |
| 43 | + password: decoded[colon + 1:].to_owned(), |
| 44 | + }) |
| 45 | +} |
| 46 | + |
| 47 | +///| |
| 48 | +/// The HTTP Basic credentials on this request (← FastAPI's `HTTPBasic` dependency), or `None`. |
| 49 | +pub fn Context::http_basic(self : Context) -> HttpBasicCredentials? { |
| 50 | + match self.request.header("authorization") { |
| 51 | + Some(h) => parse_basic_auth(h) |
| 52 | + None => None |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +///| |
| 57 | +/// The API key carried in the request header `name` (← FastAPI's `APIKeyHeader`), or `None`. Header |
| 58 | +/// names are matched against the request's lower-cased headers. |
| 59 | +pub fn Context::api_key_header(self : Context, name : String) -> String? { |
| 60 | + self.request.header(name.to_lower()) |
| 61 | +} |
| 62 | + |
| 63 | +///| |
| 64 | +/// The API key carried in the query parameter `name` (← FastAPI's `APIKeyQuery`), or `None`. |
| 65 | +pub fn Context::api_key_query(self : Context, name : String) -> String? { |
| 66 | + self.query(name) |
| 67 | +} |
| 68 | + |
| 69 | +///| |
| 70 | +/// The API key carried in the cookie `name` (← FastAPI's `APIKeyCookie`), or `None`. |
| 71 | +pub fn Context::api_key_cookie(self : Context, name : String) -> String? { |
| 72 | + self.cookie(name) |
| 73 | +} |
0 commit comments