mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-27 17:22:57 +01:00
Bearer auth
This commit is contained in:
parent
eee5365e0b
commit
716bffeb8e
@ -1,4 +1,5 @@
|
|||||||
language: rust
|
language: rust
|
||||||
|
cache: cargo
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-httpauth"
|
name = "actix-web-httpauth"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
authors = ["svartalf <self@svartalf.info>"]
|
authors = ["svartalf <self@svartalf.info>"]
|
||||||
description = "HTTP authentication schemes for actix-web"
|
description = "HTTP authentication schemes for actix-web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -12,11 +12,14 @@ categories = ["web-programming::http-server"]
|
|||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
exclude = [".travis.yml", ".gitignore"]
|
exclude = [".travis.yml", ".gitignore"]
|
||||||
|
|
||||||
[badges]
|
|
||||||
travis-ci = { repository = "svartalf/actix-web-httpauth", branch = "master" }
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "0.6"
|
actix-web = "0.6"
|
||||||
|
bytes = "0.4"
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
percent-encoding = "1.0.1"
|
|
||||||
bytes = "0.4.7"
|
[features]
|
||||||
|
default = []
|
||||||
|
nightly = []
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
travis-ci = { repository = "svartalf/actix-web-httpauth", branch = "master" }
|
||||||
|
@ -12,3 +12,4 @@ and can be used both in middlewares and request handlers, check the `examples/`
|
|||||||
## Supported schemes
|
## Supported schemes
|
||||||
|
|
||||||
* [Basic](https://tools.ietf.org/html/rfc7617)
|
* [Basic](https://tools.ietf.org/html/rfc7617)
|
||||||
|
* [Bearer](https://tools.ietf.org/html/rfc6750)
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
extern crate actix_web;
|
|
||||||
extern crate actix_web_httpauth;
|
|
||||||
|
|
||||||
use actix_web::{server, App, HttpRequest, FromRequest, Result};
|
|
||||||
use actix_web::middleware::{Middleware, Started};
|
|
||||||
use actix_web_httpauth::basic::{BasicAuth, Config};
|
|
||||||
|
|
||||||
struct AuthMiddleware;
|
|
||||||
|
|
||||||
impl<S> Middleware<S> for AuthMiddleware {
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
|
||||||
let mut config = Config::default();
|
|
||||||
config.realm("Restricted area".to_string());
|
|
||||||
let auth = BasicAuth::from_request(&req, &config)?;
|
|
||||||
|
|
||||||
// Please note that this is only an example,
|
|
||||||
// do not ever hardcode your credentials!
|
|
||||||
if auth.username == "root" && auth.password == "pass" {
|
|
||||||
Ok(Started::Done)
|
|
||||||
} else {
|
|
||||||
let response = BasicAuth::error_response(&config);
|
|
||||||
Ok(Started::Response(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(auth: BasicAuth) -> String {
|
|
||||||
format!("Hello, {}", auth.username)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
server::new(|| App::new()
|
|
||||||
// Comment the `.middleware()` line and let `BasicAuth` extractor
|
|
||||||
// in the `index` handler do the authentication routine
|
|
||||||
.middleware(AuthMiddleware)
|
|
||||||
.resource("/", |r| r.with(index)))
|
|
||||||
.bind("127.0.0.1:8088").unwrap()
|
|
||||||
.run();
|
|
||||||
}
|
|
36
examples/extractor_basic.rs
Normal file
36
examples/extractor_basic.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
extern crate actix_web;
|
||||||
|
extern crate actix_web_httpauth;
|
||||||
|
|
||||||
|
use actix_web::{server, App, Result, HttpRequest, FromRequest};
|
||||||
|
use actix_web::middleware::{Middleware, Started};
|
||||||
|
use actix_web_httpauth::extractors::basic::{BasicAuth, Config};
|
||||||
|
use actix_web_httpauth::extractors::AuthenticationError;
|
||||||
|
|
||||||
|
struct Auth;
|
||||||
|
|
||||||
|
impl<S> Middleware<S> for Auth {
|
||||||
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.realm("WallyWorld");
|
||||||
|
let auth = BasicAuth::from_request(&req, &config)?;
|
||||||
|
|
||||||
|
if auth.username() == "Aladdin" && auth.password() == Some("open sesame") {
|
||||||
|
Ok(Started::Done)
|
||||||
|
} else {
|
||||||
|
Err(AuthenticationError::from(config).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> String {
|
||||||
|
"Hello, authorized user!".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| App::new()
|
||||||
|
.middleware(Auth)
|
||||||
|
.resource("/", |r| r.with(index))
|
||||||
|
)
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
40
examples/extractor_bearer.rs
Normal file
40
examples/extractor_bearer.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
extern crate actix_web;
|
||||||
|
extern crate actix_web_httpauth;
|
||||||
|
|
||||||
|
use actix_web::{server, App, HttpRequest, Result, FromRequest};
|
||||||
|
use actix_web_httpauth::extractors::AuthenticationError;
|
||||||
|
use actix_web_httpauth::extractors::bearer::{BearerAuth, Config, Error};
|
||||||
|
use actix_web::middleware::{Middleware, Started};
|
||||||
|
|
||||||
|
struct Auth;
|
||||||
|
|
||||||
|
impl<S> Middleware<S> for Auth {
|
||||||
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.realm("Restricted area");
|
||||||
|
config.scope("openid profile email");
|
||||||
|
let auth = BearerAuth::from_request(&req, &config)?;
|
||||||
|
|
||||||
|
if auth.token() == "mF_9.B5f-4.1JqM" {
|
||||||
|
Ok(Started::Done)
|
||||||
|
} else {
|
||||||
|
Err(AuthenticationError::from(config)
|
||||||
|
.with_error(Error::InvalidToken)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> String {
|
||||||
|
"Hello, authorized user!".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| App::new()
|
||||||
|
.middleware(Auth)
|
||||||
|
.resource("/", |r| r.with(index))
|
||||||
|
)
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
25
examples/header_www_authenticate_basic.rs
Normal file
25
examples/header_www_authenticate_basic.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
extern crate actix_web;
|
||||||
|
extern crate actix_web_httpauth;
|
||||||
|
|
||||||
|
use actix_web::{server, App, HttpRequest, HttpResponse};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web_httpauth::headers::www_authenticate::{WWWAuthenticate};
|
||||||
|
use actix_web_httpauth::headers::www_authenticate::basic::Basic;
|
||||||
|
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
let challenge = Basic {
|
||||||
|
realm: Some("Restricted area".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
req.build_response(StatusCode::UNAUTHORIZED)
|
||||||
|
.set(WWWAuthenticate(challenge))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| App::new()
|
||||||
|
.resource("/", |r| r.with(index)))
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
29
examples/header_www_authenticate_bearer.rs
Normal file
29
examples/header_www_authenticate_bearer.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
extern crate actix_web;
|
||||||
|
extern crate actix_web_httpauth;
|
||||||
|
|
||||||
|
use actix_web::{server, App, HttpRequest, HttpResponse};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web_httpauth::headers::www_authenticate::{WWWAuthenticate};
|
||||||
|
use actix_web_httpauth::headers::www_authenticate::bearer::{Bearer, Error};
|
||||||
|
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
let challenge = Bearer {
|
||||||
|
realm: Some("example".to_string()),
|
||||||
|
scope: Some("openid profile email".to_string()),
|
||||||
|
error: Some(Error::InvalidToken),
|
||||||
|
error_description: Some("The access token expired".to_string()),
|
||||||
|
error_uri: Some("http://example.org".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
req.build_response(StatusCode::UNAUTHORIZED)
|
||||||
|
.set(WWWAuthenticate(challenge))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
server::new(|| App::new()
|
||||||
|
.resource("/", |r| r.with(index)))
|
||||||
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
use std::default::Default;
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
|
||||||
use percent_encoding;
|
|
||||||
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue};
|
|
||||||
|
|
||||||
/// Challenge configuration for [BasicAuth](./struct.BasicAuth.html) extractor.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
// "realm" parameter is optional now: https://tools.ietf.org/html/rfc7235#appendix-A
|
|
||||||
realm: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn realm(&mut self, value: String) -> &mut Self {
|
|
||||||
self.realm = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Bytes {
|
|
||||||
let mut bytes = Bytes::from_static(b"Basic");
|
|
||||||
if let Some(ref realm) = self.realm {
|
|
||||||
bytes.extend_from_slice(b" realm=\"");
|
|
||||||
let realm = percent_encoding::utf8_percent_encode(realm, percent_encoding::SIMPLE_ENCODE_SET);
|
|
||||||
for part in realm {
|
|
||||||
bytes.extend_from_slice(part.as_bytes());
|
|
||||||
}
|
|
||||||
bytes.extend_from_slice(b"\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Config {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
|
||||||
HeaderValue::from_bytes(&self.as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoHeaderValue for &'a Config {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
|
||||||
HeaderValue::from_bytes(&self.as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Config {
|
|
||||||
realm: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
101
src/basic/mod.rs
101
src/basic/mod.rs
@ -1,101 +0,0 @@
|
|||||||
use std::string;
|
|
||||||
use std::convert::From;
|
|
||||||
|
|
||||||
use base64;
|
|
||||||
use actix_web::{HttpRequest, HttpMessage, HttpResponse, FromRequest, ResponseError};
|
|
||||||
use actix_web::http::header;
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
|
|
||||||
use errors::Error;
|
|
||||||
pub use self::config::Config;
|
|
||||||
|
|
||||||
/// Extractor for `Authorization: Basic {payload}` HTTP request header.
|
|
||||||
///
|
|
||||||
/// If header is not present or malformed, `HTTP 401` response will be returned.
|
|
||||||
/// See [Config](./struct.Config.html) struct also.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// As a handler-level extractor:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_web_httpauth::basic::BasicAuth;
|
|
||||||
///
|
|
||||||
/// pub fn handler(auth: BasicAuth) -> String {
|
|
||||||
/// format!("Hello, {}", auth.username)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// See `examples/basic.rs` file in sources
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct BasicAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasicAuth {
|
|
||||||
pub fn error_response(cfg: &Config) -> HttpResponse {
|
|
||||||
Error::new(cfg.clone()).error_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse<S>(req: &HttpRequest<S>) -> Result<Self, ParseError> {
|
|
||||||
let header = req.headers().get(header::AUTHORIZATION)
|
|
||||||
.ok_or(ParseError)?
|
|
||||||
.to_str()?;
|
|
||||||
let mut parts = header.splitn(2, ' ');
|
|
||||||
|
|
||||||
// Authorization mechanism
|
|
||||||
match parts.next() {
|
|
||||||
Some(mechanism) if mechanism == "Basic" => (),
|
|
||||||
_ => return Err(ParseError),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization payload
|
|
||||||
let payload = parts.next().ok_or(ParseError)?;
|
|
||||||
let payload = base64::decode(payload)?;
|
|
||||||
let payload = String::from_utf8(payload)?;
|
|
||||||
let mut parts = payload.splitn(2, ':');
|
|
||||||
let user = parts.next().ok_or(ParseError)?;
|
|
||||||
let password = parts.next().ok_or(ParseError)?;
|
|
||||||
|
|
||||||
Ok(BasicAuth{
|
|
||||||
username: user.to_string(),
|
|
||||||
password: password.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<S> FromRequest<S> for BasicAuth {
|
|
||||||
type Config = Config;
|
|
||||||
type Result = Result<Self, Error>;
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest<S>, cfg: &<Self as FromRequest<S>>::Config) -> <Self as FromRequest<S>>::Result {
|
|
||||||
BasicAuth::parse(req).map_err(|_| Error::new(cfg.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ParseError;
|
|
||||||
|
|
||||||
impl From<base64::DecodeError> for ParseError {
|
|
||||||
fn from(_: base64::DecodeError) -> Self {
|
|
||||||
Self{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<header::ToStrError> for ParseError {
|
|
||||||
fn from(_: header::ToStrError) -> Self {
|
|
||||||
Self{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<string::FromUtf8Error> for ParseError {
|
|
||||||
fn from(_: string::FromUtf8Error) -> Self {
|
|
||||||
Self{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
@ -1,56 +0,0 @@
|
|||||||
use base64;
|
|
||||||
use actix_web::FromRequest;
|
|
||||||
use actix_web::test::TestRequest;
|
|
||||||
|
|
||||||
use super::BasicAuth;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_auth() {
|
|
||||||
let value = format!("Basic {}", base64::encode("user:pass"));
|
|
||||||
let req = TestRequest::with_header("Authorization", value).finish();
|
|
||||||
let auth = BasicAuth::extract(&req);
|
|
||||||
|
|
||||||
assert!(auth.is_ok());
|
|
||||||
let auth = auth.unwrap();
|
|
||||||
assert_eq!(auth.username, "user".to_string());
|
|
||||||
assert_eq!(auth.password, "pass".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_missing_header() {
|
|
||||||
let req = TestRequest::default().finish();
|
|
||||||
let auth = BasicAuth::extract(&req);
|
|
||||||
|
|
||||||
assert!(auth.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_mechanism() {
|
|
||||||
let value = format!("Digest {}", base64::encode("user:pass"));
|
|
||||||
let req = TestRequest::with_header("Authorization", value).finish();
|
|
||||||
let auth = BasicAuth::extract(&req);
|
|
||||||
|
|
||||||
assert!(auth.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_format() {
|
|
||||||
let value = format!("Basic {}", base64::encode("user"));
|
|
||||||
let req = TestRequest::with_header("Authorization", value).finish();
|
|
||||||
let auth = BasicAuth::extract(&req);
|
|
||||||
|
|
||||||
assert!(auth.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_user_without_password() {
|
|
||||||
let value = format!("Basic {}", base64::encode("user:"));
|
|
||||||
let req = TestRequest::with_header("Authorization", value).finish();
|
|
||||||
let auth = BasicAuth::extract(&req);
|
|
||||||
|
|
||||||
assert!(auth.is_ok());
|
|
||||||
assert_eq!(auth.unwrap(), BasicAuth {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "".to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::error::Error as StdError;
|
|
||||||
|
|
||||||
use actix_web::HttpResponse;
|
|
||||||
use actix_web::error::ResponseError;
|
|
||||||
use actix_web::http::{StatusCode, header};
|
|
||||||
|
|
||||||
use basic::Config;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
|
||||||
challenge: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub fn new(config: Config) -> Error {
|
|
||||||
Error {
|
|
||||||
challenge: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResponseError for Error {
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
HttpResponse::build(StatusCode::UNAUTHORIZED)
|
|
||||||
.header(header::WWW_AUTHENTICATE, &self.challenge)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdError for Error {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Unauthorized request"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.description())
|
|
||||||
}
|
|
||||||
}
|
|
80
src/extractors/basic.rs
Normal file
80
src/extractors/basic.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use actix_web::{HttpRequest, FromRequest};
|
||||||
|
use actix_web::http::header::Header;
|
||||||
|
|
||||||
|
use headers::authorization::{Authorization, Basic};
|
||||||
|
use headers::www_authenticate::basic::Basic as Challenge;
|
||||||
|
use super::errors::AuthenticationError;
|
||||||
|
use super::config::ExtractorConfig;
|
||||||
|
|
||||||
|
/// [`BasicAuth`](./struct.BasicAuth.html) extractor configuration,
|
||||||
|
/// used for `WWW-Authenticate` header later.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
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
|
||||||
|
/// [RFC2617](https://tools.ietf.org/html/rfc2617#section-1.2).
|
||||||
|
pub fn realm<T: Into<String>>(&mut self, value: T) -> &mut Config {
|
||||||
|
self.0.realm = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtractorConfig for Config {
|
||||||
|
type Inner = Challenge;
|
||||||
|
|
||||||
|
fn into_inner(self) -> Self::Inner {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Config(Challenge::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extractor for HTTP Basic auth
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix_web_httpauth;
|
||||||
|
/// use actix_web::Result;
|
||||||
|
/// use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||||
|
///
|
||||||
|
/// fn index(auth: BasicAuth) -> Result<String> {
|
||||||
|
/// Ok(format!("Hello, {}!", auth.username()))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BasicAuth(Basic);
|
||||||
|
|
||||||
|
impl BasicAuth {
|
||||||
|
pub fn username(&self) -> &str {
|
||||||
|
self.0.username.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn password(&self) -> Option<&str> {
|
||||||
|
match self.0.password {
|
||||||
|
None => None,
|
||||||
|
Some(ref pwd) => Some(pwd.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FromRequest<S> for BasicAuth {
|
||||||
|
type Config = Config;
|
||||||
|
type Result = Result<Self, AuthenticationError<Challenge>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest<S>, cfg: &<Self as FromRequest<S>>::Config) -> <Self as FromRequest<S>>::Result {
|
||||||
|
Authorization::<Basic>::parse(req)
|
||||||
|
.map(|auth| BasicAuth(auth.into_inner()))
|
||||||
|
.map_err(|_| AuthenticationError::new(cfg.0.clone()))
|
||||||
|
}
|
||||||
|
}
|
101
src/extractors/bearer.rs
Normal file
101
src/extractors/bearer.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use actix_web::{HttpRequest, FromRequest};
|
||||||
|
use actix_web::http::header::Header;
|
||||||
|
|
||||||
|
use headers::authorization;
|
||||||
|
use headers::www_authenticate::bearer;
|
||||||
|
pub use headers::www_authenticate::bearer::Error;
|
||||||
|
use super::errors::AuthenticationError;
|
||||||
|
use super::config::ExtractorConfig;
|
||||||
|
|
||||||
|
/// [BearerAuth](./struct/BearerAuth.html) extractor configuration.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Config(bearer::Bearer);
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Set challenge `scope` attribute.
|
||||||
|
///
|
||||||
|
/// The `"scope"` attribute is a space-delimited list of case-sensitive scope values
|
||||||
|
/// indicating the required scope of the access token for accessing the requested resource.
|
||||||
|
pub fn scope<T: Into<String>>(&mut self, value: T) -> &mut Config {
|
||||||
|
self.0.scope = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set challenge `realm` attribute.
|
||||||
|
///
|
||||||
|
/// The "realm" attribute indicates the scope of protection in the manner described in HTTP/1.1
|
||||||
|
/// [RFC2617](https://tools.ietf.org/html/rfc2617#section-1.2).
|
||||||
|
pub fn realm<T: Into<String>>(&mut self, value: T) -> &mut Config {
|
||||||
|
self.0.realm = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtractorConfig for Config {
|
||||||
|
type Inner = bearer::Bearer;
|
||||||
|
|
||||||
|
fn into_inner(self) -> Self::Inner {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Config(bearer::Bearer::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extractor for HTTP Bearer auth
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix_web_httpauth;
|
||||||
|
/// use actix_web::Result;
|
||||||
|
/// use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
|
///
|
||||||
|
/// fn index(auth: BearerAuth) -> Result<String> {
|
||||||
|
/// Ok(format!("Hello, user with token {}!", auth.token()))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BearerAuth(authorization::Bearer);
|
||||||
|
|
||||||
|
impl BearerAuth {
|
||||||
|
pub fn token(&self) -> &str {
|
||||||
|
self.0.token.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FromRequest<S> for BearerAuth {
|
||||||
|
type Config = Config;
|
||||||
|
type Result = Result<Self, AuthenticationError<bearer::Bearer>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest<S>, cfg: &<Self as FromRequest<S>>::Config) -> <Self as FromRequest<S>>::Result {
|
||||||
|
authorization::Authorization::<authorization::Bearer>::parse(req)
|
||||||
|
.map(|auth| BearerAuth(auth.into_inner()))
|
||||||
|
.map_err(|_| AuthenticationError::new(cfg.0.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extended error customization for HTTP `Bearer` auth.
|
||||||
|
impl AuthenticationError<bearer::Bearer> {
|
||||||
|
pub fn with_error(mut self, kind: Error) -> Self {
|
||||||
|
*self.status_code_mut() = kind.status_code();
|
||||||
|
self.challenge_mut().error = Some(kind);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_error_description<T: Into<String>>(mut self, desc: T) -> Self {
|
||||||
|
self.challenge_mut().error_description = Some(desc.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_error_uri<T: Into<String>>(mut self, uri: T) -> Self {
|
||||||
|
self.challenge_mut().error_uri = Some(uri.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
15
src/extractors/config.rs
Normal file
15
src/extractors/config.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use headers::www_authenticate::Challenge;
|
||||||
|
|
||||||
|
use super::AuthenticationError;
|
||||||
|
|
||||||
|
pub trait ExtractorConfig {
|
||||||
|
type Inner: Challenge;
|
||||||
|
|
||||||
|
fn into_inner(self) -> Self::Inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for AuthenticationError<<T as ExtractorConfig>::Inner> where T: ExtractorConfig {
|
||||||
|
fn from(config: T) -> Self {
|
||||||
|
AuthenticationError::new(config.into_inner())
|
||||||
|
}
|
||||||
|
}
|
63
src/extractors/errors.rs
Normal file
63
src/extractors/errors.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use std::str;
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use actix_web::{HttpResponse, ResponseError};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
|
||||||
|
use headers::www_authenticate::{WWWAuthenticate};
|
||||||
|
use headers::www_authenticate::Challenge;
|
||||||
|
|
||||||
|
/// Authentication error returned by Auth extractor.
|
||||||
|
///
|
||||||
|
/// Different extractors may extend `AuthenticationError` implementation
|
||||||
|
/// in order to provide access to inner challenge fields.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AuthenticationError<C: Challenge> {
|
||||||
|
challenge: C,
|
||||||
|
status_code: StatusCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Challenge> AuthenticationError<C> {
|
||||||
|
pub fn new(challenge: C) -> AuthenticationError<C> {
|
||||||
|
AuthenticationError {
|
||||||
|
challenge,
|
||||||
|
status_code: StatusCode::UNAUTHORIZED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn challenge_mut(&mut self) -> &mut C {
|
||||||
|
&mut self.challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_code_mut(&mut self) -> &mut StatusCode {
|
||||||
|
&mut self.status_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Challenge> fmt::Display for AuthenticationError<C> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let bytes = self.challenge.to_bytes();
|
||||||
|
let repr = str::from_utf8(&bytes)
|
||||||
|
// Should not happen since challenges are crafted manually
|
||||||
|
// from `&'static str`'s and Strings
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
|
|
||||||
|
f.write_str(repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: 'static + Challenge> Error for AuthenticationError<C> {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: 'static + Challenge> ResponseError for AuthenticationError<C> {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::build(self.status_code)
|
||||||
|
// TODO: Get rid of the `.clone()`
|
||||||
|
.set(WWWAuthenticate(self.challenge.clone()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
6
src/extractors/mod.rs
Normal file
6
src/extractors/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mod errors;
|
||||||
|
mod config;
|
||||||
|
pub mod basic;
|
||||||
|
pub mod bearer;
|
||||||
|
|
||||||
|
pub use self::errors::AuthenticationError;
|
70
src/headers/authorization/errors.rs
Normal file
70
src/headers/authorization/errors.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use std::str;
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::convert::From;
|
||||||
|
|
||||||
|
use base64;
|
||||||
|
use actix_web::http::header;
|
||||||
|
|
||||||
|
/// Possible errors while parsing `Authorization` header.
|
||||||
|
///
|
||||||
|
/// Should not be used directly unless you are implementing
|
||||||
|
/// your own [authentication scheme](./trait.Scheme.html).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
/// Header value is malformed
|
||||||
|
Invalid,
|
||||||
|
/// Authentication scheme is missing
|
||||||
|
MissingScheme,
|
||||||
|
/// Required authentication field is missing
|
||||||
|
MissingField(&'static str),
|
||||||
|
ToStrError(header::ToStrError),
|
||||||
|
Base64DecodeError(base64::DecodeError),
|
||||||
|
Utf8Error(str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str(self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ParseError::Invalid => "Invalid header value",
|
||||||
|
ParseError::MissingScheme => "Missing authorization scheme",
|
||||||
|
ParseError::MissingField(_) => "Missing header field",
|
||||||
|
ParseError::ToStrError(e) => e.description(),
|
||||||
|
ParseError::Base64DecodeError(e) => e.description(),
|
||||||
|
ParseError::Utf8Error(e) => e.description(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
match self {
|
||||||
|
ParseError::Invalid => None,
|
||||||
|
ParseError::MissingScheme => None,
|
||||||
|
ParseError::MissingField(_) => None,
|
||||||
|
ParseError::ToStrError(e) => Some(e),
|
||||||
|
ParseError::Base64DecodeError(e) => Some(e),
|
||||||
|
ParseError::Utf8Error(e) => Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<header::ToStrError> for ParseError {
|
||||||
|
fn from(e: header::ToStrError) -> Self {
|
||||||
|
ParseError::ToStrError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<base64::DecodeError> for ParseError {
|
||||||
|
fn from(e: base64::DecodeError) -> Self {
|
||||||
|
ParseError::Base64DecodeError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<str::Utf8Error> for ParseError {
|
||||||
|
fn from(e: str::Utf8Error) -> Self {
|
||||||
|
ParseError::Utf8Error(e)
|
||||||
|
}
|
||||||
|
}
|
85
src/headers/authorization/header.rs
Normal file
85
src/headers/authorization/header.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use std::ops;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use actix_web::{HttpMessage};
|
||||||
|
use actix_web::error::ParseError;
|
||||||
|
use actix_web::http::header::{Header, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION};
|
||||||
|
|
||||||
|
use headers::authorization::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` header is generic over [authentication scheme](./trait.Scheme.html).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix_web_httpauth;
|
||||||
|
///
|
||||||
|
/// use actix_web::{HttpRequest, Result};
|
||||||
|
/// use actix_web::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.username))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Authorization<S: Scheme>(S);
|
||||||
|
|
||||||
|
impl<S: Scheme> Authorization<S> {
|
||||||
|
pub fn into_inner(self) -> S {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Scheme> Header for Authorization<S> {
|
||||||
|
#[inline]
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
AUTHORIZATION
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
|
||||||
|
let header = msg.headers().get(AUTHORIZATION).ok_or(ParseError::Header)?;
|
||||||
|
let scheme = S::parse(header).map_err(|_| ParseError::Header)?;
|
||||||
|
|
||||||
|
Ok(Authorization(scheme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Scheme> IntoHeaderValue for Authorization<S> {
|
||||||
|
type Error = <S as IntoHeaderValue>::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
self.0.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Scheme> fmt::Display for Authorization<S> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Scheme> ops::Deref for Authorization<S> {
|
||||||
|
type Target = S;
|
||||||
|
|
||||||
|
fn deref(&self) -> &<Self as ops::Deref>::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Scheme> ops::DerefMut for Authorization<S> {
|
||||||
|
fn deref_mut(&mut self) -> &mut <Self as ops::Deref>::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
9
src/headers/authorization/mod.rs
Normal file
9
src/headers/authorization/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
mod scheme;
|
||||||
|
mod header;
|
||||||
|
mod errors;
|
||||||
|
|
||||||
|
pub use self::scheme::Scheme;
|
||||||
|
pub use self::scheme::basic::Basic;
|
||||||
|
pub use self::scheme::bearer::Bearer;
|
||||||
|
pub use self::errors::ParseError;
|
||||||
|
pub use self::header::Authorization;
|
191
src/headers/authorization/scheme/basic.rs
Normal file
191
src/headers/authorization/scheme/basic.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use std::str;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use base64;
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes};
|
||||||
|
|
||||||
|
use headers::authorization::Scheme;
|
||||||
|
use headers::authorization::errors::ParseError;
|
||||||
|
|
||||||
|
/// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617)
|
||||||
|
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct Basic {
|
||||||
|
pub username: String,
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scheme for Basic {
|
||||||
|
fn parse(header: &HeaderValue) -> Result<Self, ParseError> {
|
||||||
|
// "Basic *" length
|
||||||
|
if header.len() < 7 {
|
||||||
|
return Err(ParseError::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = header.to_str()?.splitn(2, ' ');
|
||||||
|
match parts.next() {
|
||||||
|
Some(scheme) if scheme == "Basic" => (),
|
||||||
|
_ => return Err(ParseError::MissingScheme),
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = base64::decode(parts.next().ok_or(ParseError::Invalid)?)?;
|
||||||
|
let mut credentials = str::from_utf8(&decoded)?
|
||||||
|
.splitn(2, ':');
|
||||||
|
|
||||||
|
let username = credentials.next()
|
||||||
|
.ok_or(ParseError::MissingField("username"))
|
||||||
|
.map(|username| username.to_string())?;
|
||||||
|
let password = credentials.next()
|
||||||
|
.ok_or(ParseError::MissingField("password"))
|
||||||
|
.map(|password| {
|
||||||
|
if password.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(password.to_string())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Basic{
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Basic {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_fmt(format_args!("Basic {}:******", self.username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Basic {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO: Display password also
|
||||||
|
f.write_fmt(format_args!("Basic {}:******", self.username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Basic {
|
||||||
|
type Error = InvalidHeaderValueBytes;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
let mut credentials = BytesMut::with_capacity(
|
||||||
|
self.username.len() + self.password.as_ref().map_or(0, |pwd| pwd.len())
|
||||||
|
);
|
||||||
|
credentials.put(&self.username);
|
||||||
|
credentials.put_u8(b':');
|
||||||
|
if let Some(ref password) = self.password {
|
||||||
|
credentials.put(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: It would be nice not to allocate new `String` here but write directly to `value`
|
||||||
|
let encoded = base64::encode(&credentials);
|
||||||
|
let mut value = BytesMut::with_capacity(6 + encoded.len());
|
||||||
|
value.put("Basic ");
|
||||||
|
value.put(&encoded);
|
||||||
|
|
||||||
|
HeaderValue::from_shared(value.freeze())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue};
|
||||||
|
use super::{Scheme, Basic};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_header() {
|
||||||
|
let value = HeaderValue::from_static("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_ok());
|
||||||
|
let scheme = scheme.unwrap();
|
||||||
|
assert_eq!(scheme.username, "Aladdin");
|
||||||
|
assert_eq!(scheme.password, Some("open sesame".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_password() {
|
||||||
|
let value = HeaderValue::from_static("Basic QWxhZGRpbjo=");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_ok());
|
||||||
|
let scheme = scheme.unwrap();
|
||||||
|
assert_eq!(scheme.username, "Aladdin");
|
||||||
|
assert_eq!(scheme.password, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_header() {
|
||||||
|
let value = HeaderValue::from_static("");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_scheme() {
|
||||||
|
let value = HeaderValue::from_static("THOUSHALLNOTPASS please?");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_credentials() {
|
||||||
|
let value = HeaderValue::from_static("Basic ");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_credentials_colon() {
|
||||||
|
let value = HeaderValue::from_static("Basic QWxsYWRpbg==");
|
||||||
|
let scheme = Basic::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_header_value() {
|
||||||
|
let basic = Basic {
|
||||||
|
username: "Aladdin".to_string(),
|
||||||
|
password: Some("open sesame".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = basic.try_into();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), HeaderValue::from_static("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "nightly"))]
|
||||||
|
mod benches {
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue};
|
||||||
|
|
||||||
|
use super::{Basic, Scheme};
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_parsing(b: &mut Bencher) {
|
||||||
|
let value = HeaderValue::from_static("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
|
b.iter(|| {
|
||||||
|
Basic::parse(&value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_serializing(b: &mut Bencher) {
|
||||||
|
b.iter(|| {
|
||||||
|
let basic = Basic {
|
||||||
|
username: "Aladdin".to_string(),
|
||||||
|
password: Some("open sesame".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
basic.try_into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
111
src/headers/authorization/scheme/bearer.rs
Normal file
111
src/headers/authorization/scheme/bearer.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes};
|
||||||
|
|
||||||
|
use headers::authorization::scheme::Scheme;
|
||||||
|
use headers::authorization::errors::ParseError;
|
||||||
|
|
||||||
|
/// Credentials for `Bearer` authentication scheme, defined in [RFC6750](https://tools.ietf.org/html/rfc6750)
|
||||||
|
///
|
||||||
|
/// Should be used in combination with [`Authorization`](./struct.Authorization.html) header.
|
||||||
|
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct Bearer {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scheme for Bearer {
|
||||||
|
fn parse(header: &HeaderValue) -> Result<Self, ParseError> {
|
||||||
|
// "Bearer *" length
|
||||||
|
if header.len() < 8 {
|
||||||
|
return Err(ParseError::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = header.to_str()?.splitn(2, ' ');
|
||||||
|
match parts.next() {
|
||||||
|
Some(scheme) if scheme == "Bearer" => (),
|
||||||
|
_ => return Err(ParseError::MissingScheme),
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = parts.next().ok_or(ParseError::Invalid)?;
|
||||||
|
|
||||||
|
Ok(Bearer{
|
||||||
|
token: token.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Bearer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_fmt(format_args!("Bearer ******"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Bearer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_fmt(format_args!("Bearer {}", self.token))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Bearer {
|
||||||
|
type Error = InvalidHeaderValueBytes;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
let mut buffer = BytesMut::with_capacity(7 + self.token.len());
|
||||||
|
buffer.put("Bearer ");
|
||||||
|
buffer.put(self.token);
|
||||||
|
|
||||||
|
HeaderValue::from_shared(buffer.freeze())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue};
|
||||||
|
use super::{Scheme, Bearer};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_header() {
|
||||||
|
let value = HeaderValue::from_static("Bearer mF_9.B5f-4.1JqM");
|
||||||
|
let scheme = Bearer::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_ok());
|
||||||
|
let scheme = scheme.unwrap();
|
||||||
|
assert_eq!(scheme.token, "mF_9.B5f-4.1JqM");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_header() {
|
||||||
|
let value = HeaderValue::from_static("");
|
||||||
|
let scheme = Bearer::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_scheme() {
|
||||||
|
let value = HeaderValue::from_static("OAuthToken foo");
|
||||||
|
let scheme = Bearer::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_token() {
|
||||||
|
let value = HeaderValue::from_static("Bearer ");
|
||||||
|
let scheme = Bearer::parse(&value);
|
||||||
|
|
||||||
|
assert!(scheme.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_header_value() {
|
||||||
|
let bearer = Bearer {
|
||||||
|
token: "mF_9.B5f-4.1JqM".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = bearer.try_into();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), HeaderValue::from_static("Bearer mF_9.B5f-4.1JqM"));
|
||||||
|
}
|
||||||
|
}
|
13
src/headers/authorization/scheme/mod.rs
Normal file
13
src/headers/authorization/scheme/mod.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use actix_web::http::header::{IntoHeaderValue, HeaderValue};
|
||||||
|
|
||||||
|
pub mod basic;
|
||||||
|
pub mod bearer;
|
||||||
|
|
||||||
|
use headers::authorization::errors::ParseError;
|
||||||
|
|
||||||
|
/// Authentication scheme for [`Authorization`](./struct.Authorization.html) header.
|
||||||
|
pub trait Scheme: IntoHeaderValue + Debug + Display + Clone + Send + Sync {
|
||||||
|
fn parse(header: &HeaderValue) -> Result<Self, ParseError>;
|
||||||
|
}
|
2
src/headers/mod.rs
Normal file
2
src/headers/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod authorization;
|
||||||
|
pub mod www_authenticate;
|
91
src/headers/www_authenticate/challenge/basic.rs
Normal file
91
src/headers/www_authenticate/challenge/basic.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use std::str;
|
||||||
|
use std::fmt;
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes};
|
||||||
|
|
||||||
|
use super::Challenge;
|
||||||
|
|
||||||
|
/// Challenge for `WWW-Authenticate` header with HTTP Basic auth scheme,
|
||||||
|
/// described in [RFC 7617](https://tools.ietf.org/html/rfc7617)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Basic {
|
||||||
|
// "realm" parameter is optional now: https://tools.ietf.org/html/rfc7235#appendix-A
|
||||||
|
pub realm: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Challenge for Basic {
|
||||||
|
fn to_bytes(&self) -> Bytes {
|
||||||
|
// 5 is for `"Basic"`, 9 is for `"realm=\"\""`
|
||||||
|
let length = 5 + self.realm.as_ref().map_or(0, |realm| realm.len() + 9);
|
||||||
|
let mut buffer = BytesMut::with_capacity(length);
|
||||||
|
buffer.put("Basic");
|
||||||
|
if let Some(ref realm) = self.realm {
|
||||||
|
buffer.put(" realm=\"");
|
||||||
|
buffer.put(realm);
|
||||||
|
buffer.put("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Basic {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let bytes = self.to_bytes();
|
||||||
|
let repr = str::from_utf8(&bytes)
|
||||||
|
// Should not happen since challenges are crafted manually
|
||||||
|
// from `&'static str`'s and Strings
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
|
|
||||||
|
f.write_str(repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Basic {
|
||||||
|
type Error = InvalidHeaderValueBytes;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
HeaderValue::from_shared(self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Default for Basic {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
realm: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Basic;
|
||||||
|
use actix_web::http::header::IntoHeaderValue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_plain_into_header_value() {
|
||||||
|
let challenge = Basic {
|
||||||
|
realm: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = challenge.try_into();
|
||||||
|
assert!(value.is_ok());
|
||||||
|
let value = value.unwrap();
|
||||||
|
assert_eq!(value, "Basic");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_realm_into_header_value() {
|
||||||
|
let challenge = Basic {
|
||||||
|
realm: Some("Restricted area".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = challenge.try_into();
|
||||||
|
assert!(value.is_ok());
|
||||||
|
let value = value.unwrap();
|
||||||
|
assert_eq!(value, "Basic realm=\"Restricted area\"");
|
||||||
|
}
|
||||||
|
}
|
136
src/headers/www_authenticate/challenge/bearer.rs
Normal file
136
src/headers/www_authenticate/challenge/bearer.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use std::str;
|
||||||
|
use std::fmt;
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes};
|
||||||
|
|
||||||
|
use super::Challenge;
|
||||||
|
|
||||||
|
/// Bearer authorization error types, described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3.1)
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Error {
|
||||||
|
/// The request is missing a required parameter, includes an unsupported parameter
|
||||||
|
/// or parameter value, repeats the same parameter, uses more than one method
|
||||||
|
/// for including an access token, or is otherwise malformed.
|
||||||
|
InvalidRequest,
|
||||||
|
|
||||||
|
/// The access token provided is expired, revoked, malformed, or invalid for other reasons.
|
||||||
|
InvalidToken,
|
||||||
|
|
||||||
|
/// The request requires higher privileges than provided by the access token.
|
||||||
|
InsufficientScope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn status_code(&self) -> StatusCode {
|
||||||
|
match *self {
|
||||||
|
Error::InvalidRequest => StatusCode::BAD_REQUEST,
|
||||||
|
Error::InvalidToken => StatusCode::UNAUTHORIZED,
|
||||||
|
Error::InsufficientScope => StatusCode::FORBIDDEN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
Error::InvalidRequest => "invalid_request",
|
||||||
|
Error::InvalidToken => "invalid_token",
|
||||||
|
Error::InsufficientScope => "insufficient_scope",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Challenge for `WWW-Authenticate` header with HTTP Bearer auth scheme,
|
||||||
|
/// described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Bearer {
|
||||||
|
pub scope: Option<String>,
|
||||||
|
pub realm: Option<String>,
|
||||||
|
pub error: Option<Error>,
|
||||||
|
pub error_description: Option<String>,
|
||||||
|
/// It is up to implementor to provide correct absolute URI
|
||||||
|
pub error_uri: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Challenge for Bearer {
|
||||||
|
fn to_bytes(&self) -> Bytes {
|
||||||
|
let capacity = 6 +
|
||||||
|
self.realm.as_ref().map_or(0, |realm| realm.len() + 9) +
|
||||||
|
self.scope.as_ref().map_or(0, |scope| scope.len() + 9) +
|
||||||
|
self.error_description.as_ref().map_or(0, |desc| desc.len() + 20) +
|
||||||
|
self.error_uri.as_ref().map_or(0, |url| url.len() + 12);
|
||||||
|
let mut buffer = BytesMut::with_capacity(capacity);
|
||||||
|
buffer.put("Bearer");
|
||||||
|
|
||||||
|
if let Some(ref realm) = self.realm {
|
||||||
|
buffer.put(" realm=\"");
|
||||||
|
buffer.put(realm);
|
||||||
|
buffer.put("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref scope) = self.scope {
|
||||||
|
buffer.put(" scope=\"");
|
||||||
|
buffer.put(scope);
|
||||||
|
buffer.put("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref error) = self.error {
|
||||||
|
let error_repr = error.as_str();
|
||||||
|
let remaining = buffer.remaining_mut();
|
||||||
|
let required = error_repr.len() + 9; // 9 is for `" error=\"\""`
|
||||||
|
if remaining < required {
|
||||||
|
buffer.reserve(required - remaining);
|
||||||
|
}
|
||||||
|
buffer.put(" error=\"");
|
||||||
|
buffer.put(error_repr);
|
||||||
|
buffer.put("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref error_description) = self.error_description {
|
||||||
|
buffer.put(" error_description=\"");
|
||||||
|
buffer.put(error_description);
|
||||||
|
buffer.put("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref error_uri) = self.error_uri {
|
||||||
|
buffer.put(" error_uri=\"");
|
||||||
|
buffer.put(error_uri);
|
||||||
|
buffer.put("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bearer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Bearer {
|
||||||
|
scope: None,
|
||||||
|
realm: None,
|
||||||
|
error: None,
|
||||||
|
error_description: None,
|
||||||
|
error_uri: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Bearer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let bytes = self.to_bytes();
|
||||||
|
let repr = str::from_utf8(&bytes)
|
||||||
|
// Should not happen since challenges are crafted manually
|
||||||
|
// from `&'static str`'s and Strings
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
|
|
||||||
|
f.write_str(repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Bearer {
|
||||||
|
type Error = InvalidHeaderValueBytes;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
HeaderValue::from_shared(self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
12
src/headers/www_authenticate/challenge/mod.rs
Normal file
12
src/headers/www_authenticate/challenge/mod.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use actix_web::http::header::IntoHeaderValue;
|
||||||
|
|
||||||
|
pub mod basic;
|
||||||
|
pub mod bearer;
|
||||||
|
|
||||||
|
/// Authentication challenge for `WWW-Authenticate` header.
|
||||||
|
pub trait Challenge: IntoHeaderValue + Debug + Display + Clone + Send + Sync {
|
||||||
|
fn to_bytes(&self) -> Bytes;
|
||||||
|
}
|
49
src/headers/www_authenticate/header.rs
Normal file
49
src/headers/www_authenticate/header.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use actix_web::{HttpMessage};
|
||||||
|
use actix_web::error::ParseError;
|
||||||
|
use actix_web::http::header::{Header, HeaderName, HeaderValue, IntoHeaderValue, WWW_AUTHENTICATE};
|
||||||
|
|
||||||
|
use super::Challenge;
|
||||||
|
|
||||||
|
/// `WWW-Authenticate` header, described in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-4.1)
|
||||||
|
///
|
||||||
|
/// `WWW-Authenticate` header is generic over [Challenge](./trait.Challenge.html)
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix_web_httpauth;
|
||||||
|
///
|
||||||
|
/// use actix_web::{HttpRequest, HttpResponse};
|
||||||
|
/// use actix_web::http::StatusCode;
|
||||||
|
/// use actix_web_httpauth::headers::www_authenticate::{WWWAuthenticate};
|
||||||
|
/// use actix_web_httpauth::headers::www_authenticate::basic::Basic;
|
||||||
|
///
|
||||||
|
/// fn handler(req: HttpRequest) -> HttpResponse {
|
||||||
|
/// let challenge = Basic {
|
||||||
|
/// realm: Some("Restricted area".to_string()),
|
||||||
|
/// };
|
||||||
|
/// req.build_response(StatusCode::UNAUTHORIZED)
|
||||||
|
/// .set(WWWAuthenticate(challenge))
|
||||||
|
/// .finish()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct WWWAuthenticate<C: Challenge>(pub C);
|
||||||
|
|
||||||
|
impl<C: Challenge> Header for WWWAuthenticate<C> {
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
WWW_AUTHENTICATE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<T: HttpMessage>(_msg: &T) -> Result<Self, ParseError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Challenge> IntoHeaderValue for WWWAuthenticate<C> {
|
||||||
|
type Error = <C as IntoHeaderValue>::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
|
self.0.try_into()
|
||||||
|
}
|
||||||
|
}
|
7
src/headers/www_authenticate/mod.rs
Normal file
7
src/headers/www_authenticate/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mod challenge;
|
||||||
|
mod header;
|
||||||
|
|
||||||
|
pub use self::header::WWWAuthenticate;
|
||||||
|
pub use self::challenge::Challenge;
|
||||||
|
pub use self::challenge::basic;
|
||||||
|
pub use self::challenge::bearer;
|
17
src/lib.rs
17
src/lib.rs
@ -1,12 +1,15 @@
|
|||||||
//! HTTP authorization routines for [actix-web](https://github.com/actix/actix-web) framework.
|
//! HTTP Authorization support for [actix-web](https://actix.rs) framework.
|
||||||
//!
|
//!
|
||||||
//! Currently supported schemas:
|
//! Provides [`Authorization`](./headers/authorization/struct.Authorization.html)
|
||||||
//! * Basic ([RFC-7617](https://tools.ietf.org/html/rfc7617))
|
//! and [`WWW-Authenticate`](./headers/www_authenticate/struct.WWWAuthenticate.html) headers,
|
||||||
|
//! and `actix-web` extractors for an `Authorization` header.
|
||||||
|
|
||||||
|
#![cfg_attr(feature = "nightly", feature(test))]
|
||||||
|
#[cfg(feature = "nightly")] extern crate test;
|
||||||
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate percent_encoding;
|
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
|
extern crate bytes;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
|
||||||
mod errors;
|
pub mod headers;
|
||||||
pub mod basic;
|
pub mod extractors;
|
||||||
|
Loading…
Reference in New Issue
Block a user