-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip, implements simple api-key header extraction for middleware verif… #1
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
//! Extractor for the "Basic" HTTP Authentication Scheme. | ||
|
||
use std::borrow::Cow; | ||
|
||
use actix_utils::future::{ready, Ready}; | ||
use actix_web::{dev::Payload, http::header::Header, FromRequest, HttpRequest}; | ||
|
||
use super::{config::AuthExtractorConfig, errors::AuthenticationError}; | ||
use crate::headers::{ | ||
api_key::{APIKey, XAPIKey}, | ||
www_authenticate::basic::Basic as Challenge, | ||
}; | ||
|
||
/// [`BasicAuth`] extractor configuration used for [`WWW-Authenticate`] header later. | ||
/// | ||
/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate | ||
#[derive(Debug, Clone, Default)] | ||
pub struct Config(Challenge); | ||
|
||
impl Config { | ||
/// Set challenge `realm` attribute. | ||
/// | ||
/// The "realm" attribute indicates the scope of protection in the manner described in HTTP/1.1 | ||
/// [RFC 2617 §1.2](https://tools.ietf.org/html/rfc2617#section-1.2). | ||
pub fn realm<T>(mut self, value: T) -> Config | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect this is not valid for API-Key auth. However I see the The config that would be useful is what is the name of the header containing the api-key. i.e. it shouldnt be hard-coded to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re Bearer realm - it is allowed in the spec https://datatracker.ietf.org/doc/html/rfc6750#section-3 wrt X-API-Key, we should see if there is anyone using realm with this header |
||
where | ||
T: Into<Cow<'static, str>>, | ||
{ | ||
self.0.realm = Some(value.into()); | ||
self | ||
} | ||
} | ||
|
||
impl AsRef<Challenge> for Config { | ||
fn as_ref(&self) -> &Challenge { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl AuthExtractorConfig for Config { | ||
type Inner = Challenge; | ||
|
||
fn into_inner(self) -> Self::Inner { | ||
self.0 | ||
} | ||
} | ||
|
||
/// Extractor for HTTP Basic auth. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// use actix_web_httpauth::extractors::basic::BasicAuth; | ||
/// | ||
/// async fn index(auth: BasicAuth) -> String { | ||
/// format!("Hello, {}!", auth.user_id()) | ||
/// } | ||
/// ``` | ||
/// | ||
/// If authentication fails, this extractor fetches the [`Config`] instance from the [app data] in | ||
/// order to properly form the `WWW-Authenticate` response header. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// use actix_web::{web, App}; | ||
/// use actix_web_httpauth::extractors::basic::{self, BasicAuth}; | ||
/// | ||
/// async fn index(auth: BasicAuth) -> String { | ||
/// format!("Hello, {}!", auth.user_id()) | ||
/// } | ||
/// | ||
/// App::new() | ||
/// .app_data(basic::Config::default().realm("Restricted area")) | ||
/// .service(web::resource("/index.html").route(web::get().to(index))); | ||
/// ``` | ||
/// | ||
/// [app data]: https://docs.rs/actix-web/4/actix_web/struct.App.html#method.app_data | ||
#[derive(Debug, Clone)] | ||
pub struct APIKeyAuth(APIKey); | ||
|
||
impl APIKeyAuth { | ||
/// Returns client's user-ID. | ||
pub fn api_key(&self) -> &str { | ||
self.0.api_key() | ||
} | ||
} | ||
|
||
impl From<APIKey> for APIKeyAuth { | ||
fn from(api_key: APIKey) -> Self { | ||
Self(api_key) | ||
} | ||
} | ||
|
||
impl FromRequest for APIKeyAuth { | ||
type Future = Ready<Result<Self, Self::Error>>; | ||
type Error = AuthenticationError<Challenge>; | ||
|
||
fn from_request(req: &HttpRequest, _: &mut Payload) -> <Self as FromRequest>::Future { | ||
ready( | ||
XAPIKey::<APIKey>::parse(req) | ||
.map(|auth| APIKeyAuth(auth.into_scheme())) | ||
.map_err(|err| { | ||
log::debug!("`APIKeAuth` extract error: {}", err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. APIKeAuth->APIKeyAuth |
||
|
||
let challenge = req | ||
.app_data::<Config>() | ||
.map(|config| config.0.clone()) | ||
.unwrap_or_default(); | ||
|
||
AuthenticationError::new(challenge) | ||
}), | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
use std::fmt; | ||
|
||
use actix_web::{ | ||
error::ParseError, | ||
http::header::{Header, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}, | ||
HttpMessage, | ||
}; | ||
|
||
use crate::headers::api_key::scheme::Scheme; | ||
|
||
/// `Authorization` header, defined in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-4.2) | ||
/// | ||
/// The "Authorization" header field allows a user agent to authenticate itself with an origin | ||
/// server—usually, but not necessarily, after receiving a 401 (Unauthorized) response. Its value | ||
/// consists of credentials containing the authentication information of the user agent for the | ||
/// realm of the resource being requested. | ||
/// | ||
/// `Authorization` is generic over an [authentication scheme](Scheme). | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// # use actix_web::{HttpRequest, Result, http::header::Header}; | ||
/// # use actix_web_httpauth::headers::authorization::{Authorization, Basic}; | ||
/// fn handler(req: HttpRequest) -> Result<String> { | ||
/// let auth = Authorization::<Basic>::parse(&req)?; | ||
/// | ||
/// Ok(format!("Hello, {}!", auth.as_ref().user_id())) | ||
/// } | ||
/// ``` | ||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
pub struct XAPIKey<S: Scheme>(S); | ||
|
||
impl<S: Scheme> XAPIKey<S> { | ||
/// Consumes `X-API-KEY` header and returns inner [`Scheme`] implementation. | ||
pub fn into_scheme(self) -> S { | ||
self.0 | ||
} | ||
} | ||
|
||
impl<S: Scheme> From<S> for XAPIKey<S> { | ||
fn from(scheme: S) -> XAPIKey<S> { | ||
XAPIKey(scheme) | ||
} | ||
} | ||
|
||
impl<S: Scheme> AsRef<S> for XAPIKey<S> { | ||
fn as_ref(&self) -> &S { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<S: Scheme> AsMut<S> for XAPIKey<S> { | ||
fn as_mut(&mut self) -> &mut S { | ||
&mut self.0 | ||
} | ||
} | ||
|
||
impl<S: Scheme> fmt::Display for XAPIKey<S> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
fmt::Display::fmt(&self.0, f) | ||
} | ||
} | ||
|
||
impl<S: Scheme> Header for XAPIKey<S> { | ||
#[inline] | ||
fn name() -> HeaderName { | ||
HeaderName::from_static("x-api-key") | ||
} | ||
|
||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> { | ||
let header = msg.headers().get(Self::name()).ok_or(ParseError::Header)?; | ||
let scheme = S::parse(header).map_err(|_| ParseError::Header)?; | ||
|
||
Ok(XAPIKey(scheme)) | ||
} | ||
} | ||
|
||
impl<S: Scheme> TryIntoHeaderValue for XAPIKey<S> { | ||
type Error = <S as TryIntoHeaderValue>::Error; | ||
|
||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> { | ||
self.0.try_into_value() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
//! `Authorization` header and various auth schemes. | ||
|
||
mod header; | ||
mod scheme; | ||
|
||
pub use self::{ | ||
header::XAPIKey, | ||
scheme::{api_key::APIKey, Scheme}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use std::{borrow::Cow, fmt, str}; | ||
|
||
use actix_web::http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}; | ||
|
||
use crate::headers::api_key::Scheme; | ||
use crate::headers::errors::ParseError; | ||
|
||
/// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617) | ||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] | ||
pub struct APIKey { | ||
api_key: Cow<'static, str>, | ||
} | ||
|
||
impl APIKey { | ||
/// Creates `Basic` credentials with provided `user_id` and optional | ||
/// `password`. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// # use actix_web_httpauth::headers::authorization::Basic; | ||
/// let credentials = Basic::new("Alladin", Some("open sesame")); | ||
/// ``` | ||
pub fn new<U>(api_key: U) -> APIKey | ||
where | ||
U: Into<Cow<'static, str>>, | ||
{ | ||
APIKey { | ||
api_key: api_key.into(), | ||
} | ||
} | ||
|
||
/// Returns client's user-ID. | ||
pub fn api_key(&self) -> &str { | ||
&self.api_key.as_ref() | ||
} | ||
} | ||
|
||
impl Scheme for APIKey { | ||
fn parse(header: &HeaderValue) -> Result<Self, ParseError> { | ||
// "Basic *" length | ||
if header.len() < 36 { | ||
return Err(ParseError::Invalid); | ||
} | ||
let api_key = header.to_str()?.to_string(); | ||
|
||
Ok(APIKey::new(api_key)) | ||
} | ||
} | ||
|
||
impl fmt::Debug for APIKey { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_fmt(format_args!("APIKey: ******")) | ||
} | ||
} | ||
|
||
impl fmt::Display for APIKey { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_fmt(format_args!("APIKey: ******")) | ||
} | ||
} | ||
|
||
impl TryIntoHeaderValue for APIKey { | ||
type Error = InvalidHeaderValue; | ||
|
||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> { | ||
let value = String::from(self.api_key); | ||
HeaderValue::from_maybe_shared(value) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_parse_header() { | ||
let key = "0451f2f1-74a7-4b8c-994d-2f67675ba07c"; | ||
let value = HeaderValue::from_static(key); | ||
let scheme = APIKey::parse(&value); | ||
|
||
assert!(scheme.is_ok()); | ||
let scheme = scheme.unwrap(); | ||
assert_eq!(scheme.api_key, key); | ||
} | ||
|
||
#[test] | ||
fn test_empty_header() { | ||
let value = HeaderValue::from_static(""); | ||
let scheme = APIKey::parse(&value); | ||
|
||
assert!(scheme.is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_wrong_scheme() { | ||
let value = HeaderValue::from_static("THOUSHALLNOTPASS please?"); | ||
let scheme = APIKey::parse(&value); | ||
|
||
assert!(scheme.is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_into_header_value() { | ||
let key = "0451f2f1-74a7-4b8c-994d-2f67675ba07c"; | ||
let basic = APIKey { | ||
api_key: key.into(), | ||
}; | ||
|
||
let result = basic.try_into_value(); | ||
assert!(result.is_ok()); | ||
assert_eq!(result.unwrap(), HeaderValue::from_static(key)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use std::fmt::{Debug, Display}; | ||
|
||
use actix_web::http::header::{HeaderValue, TryIntoHeaderValue}; | ||
|
||
pub mod api_key; | ||
|
||
use crate::headers::errors::ParseError; | ||
|
||
/// Authentication scheme for [`Authorization`](super::Authorization) header. | ||
pub trait Scheme: TryIntoHeaderValue + Debug + Display + Clone + Send + Sync { | ||
/// Try to parse an authentication scheme from the `Authorization` header. | ||
fn parse(header: &HeaderValue) -> Result<Self, ParseError>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,9 @@ | ||
//! `Authorization` header and various auth schemes. | ||
|
||
mod errors; | ||
mod header; | ||
mod scheme; | ||
|
||
pub use self::{ | ||
errors::ParseError, | ||
header::Authorization, | ||
scheme::{basic::Basic, bearer::Bearer, Scheme}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
//! Typed HTTP headers. | ||
|
||
pub mod api_key; | ||
pub mod authorization; | ||
/// | ||
pub mod errors; | ||
pub mod www_authenticate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Basic/API-Key/ in all the docs