1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-27 09:12:57 +01:00

fix doctest ci (#188)

This commit is contained in:
Rob Ede 2021-06-27 07:02:38 +01:00 committed by GitHub
parent 64eec6e550
commit 20ef05c36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 255 additions and 206 deletions

9
.cargo/config.toml Normal file
View File

@ -0,0 +1,9 @@
[alias]
chk = "check --workspace --all-features --tests --examples --bins"
lint = "clippy --workspace --tests --examples"
ci-min = "hack check --workspace --no-default-features"
ci-min-test = "hack check --workspace --no-default-features --tests --examples"
ci-default = "check --workspace --bins --tests --examples"
ci-full = "check --workspace --all-features --bins --tests --examples"
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast"
ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast"

View File

@ -31,21 +31,6 @@ jobs:
steps:
- uses: actions/checkout@v2
# # install OpenSSL on Windows
# - name: Set vcpkg root
# if: matrix.target.triple == 'x86_64-pc-windows-msvc'
# run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
# - name: Install OpenSSL
# if: matrix.target.triple == 'x86_64-pc-windows-msvc'
# run: vcpkg install openssl:x64-windows
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
@ -68,21 +53,32 @@ jobs:
- name: check minimal
uses: actions-rs/cargo@v1
with:
command: hack
args: --clean-per-run check --workspace --no-default-features --tests
with: { command: ci-min }
- name: check minimal + tests
uses: actions-rs/cargo@v1
with: { command: ci-min-test }
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-default }
- name: check full
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
with: { command: ci-full }
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with: { command: ci-test }
# only run on Linux due to unknown issue with running doc tests on macOS
- name: doc tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: -v --workspace --all-features --no-fail-fast -- --nocapture
command: ci-doctest
args: -- --nocapture
- name: Generate coverage file
if: >
@ -101,7 +97,7 @@ jobs:
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache
build_and_test_other:
@ -122,21 +118,6 @@ jobs:
steps:
- uses: actions/checkout@v2
# # install OpenSSL on Windows
# - name: Set vcpkg root
# if: matrix.target.triple == 'x86_64-pc-windows-msvc'
# run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
# - name: Install OpenSSL
# if: matrix.target.triple == 'x86_64-pc-windows-msvc'
# run: vcpkg install openssl:x64-windows
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
@ -159,26 +140,28 @@ jobs:
- name: check minimal
uses: actions-rs/cargo@v1
with:
command: hack
args: --clean-per-run check --workspace --no-default-features --tests
with: { command: ci-min }
- name: check minimal + tests
uses: actions-rs/cargo@v1
with: { command: ci-min-test }
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-default }
- name: check full
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
with: { command: ci-full }
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: -v
--workspace --all-features --no-fail-fast
--exclude=actix-redis
-- --nocapture
command: ci-test
args: --exclude=actix-redis -- --nocapture
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache

View File

@ -1,12 +1,13 @@
[workspace]
members = [
"actix-cors",
"actix-identity",
"actix-protobuf",
"actix-protobuf/examples/prost-example",
"actix-redis",
"actix-session",
"actix-web-httpauth",
"actix-cors",
"actix-identity",
"actix-protobuf",
# TODO: move this example to examples repo
# "actix-protobuf/examples/prost-example",
"actix-redis",
"actix-session",
"actix-web-httpauth",
]
[patch.crates-io]

View File

@ -28,8 +28,8 @@ These crates are provided by the community.
| [actix-limitation] | [![crates.io](https://img.shields.io/crates/v/actix-limitation?label=latest)](https://crates.io/crates/actix-limitation) [![Documentation](https://docs.rs/actix-limitation/badge.svg)](https://docs.rs/actix-limitation) [![dependency status](https://deps.rs/crate/actix-limitation/0.1.4/status.svg)](https://deps.rs/crate/actix-limitation/0.1.4) | Rate-limiting using a fixed window counter for arbitrary keys, backed by Redis. |
| [actix-casbin] | [![crates.io](https://img.shields.io/crates/v/actix-casbin?label=latest)](https://crates.io/crates/actix-casbin) [![Documentation](https://docs.rs/actix-casbin/badge.svg)](https://docs.rs/actix-casbin) [![dependency status](https://deps.rs/crate/actix-casbin/0.4.2/status.svg)](https://deps.rs/crate/actix-casbin/0.4.2) | Authorization library that supports access control models like ACL, RBAC & ABAC. |
| [actix-web-static-files] | [![crates.io](https://img.shields.io/crates/v/actix-web-static-files?label=latest)](https://crates.io/crates/actix-web-static-files) [![Documentation](https://docs.rs/actix-web-static-files/badge.svg)](https://docs.rs/actix-web-static-files) [![dependency status](https://deps.rs/crate/actix-web-static-files/3.0.5/status.svg)](https://deps.rs/crate/actix-web-static-files/3.0.5) | Static files as embedded resources. |
| [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)](https://crates.io/crates/actix-web-grants) [![Documentation](https://docs.rs/actix-web-grants/badge.svg)](https://docs.rs/actix-web-grants) [![dependency status](https://deps.rs/crate/actix-web-grants/2.0.1/status.svg)](https://deps.rs/crate/actix-web-grants) | Extension for validating user authorities. |
| [aliri_actix] | [![crates.io](https://img.shields.io/crates/v/aliri_actix?label=latest)](https://crates.io/crates/aliri_actix) [![Documentation](https://docs.rs/aliri_actix/badge.svg)](https://docs.rs/aliri_actix) [![dependency status](https://deps.rs/crate/aliri_actix/0.5.0/status.svg)](https://deps.rs/crate/aliri_actix/0.5.0) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. |
| [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)](https://crates.io/crates/actix-web-grants) [![Documentation](https://docs.rs/actix-web-grants/badge.svg)](https://docs.rs/actix-web-grants) [![dependency status](https://deps.rs/crate/actix-web-grants/2.0.1/status.svg)](https://deps.rs/crate/actix-web-grants/2.0.1) | Extension for validating user authorities. |
| [aliri_actix] | [![crates.io](https://img.shields.io/crates/v/aliri_actix?label=latest)](https://crates.io/crates/aliri_actix) [![Documentation](https://docs.rs/aliri_actix/badge.svg)](https://docs.rs/aliri_actix) [![dependency status](https://deps.rs/crate/aliri_actix/0.5.0/status.svg)](https://deps.rs/crate/aliri_actix/0.5.0) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. |
To add a crate to this list, submit a pull request.

View File

@ -6,11 +6,9 @@ authors = [
"Rob Ede <robjtede@icloud.com>",
]
description = "Cross-Origin Resource Sharing (CORS) controls for Actix Web"
readme = "README.md"
keywords = ["actix", "cors", "web", "security", "crossorigin"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
documentation = "https://docs.rs/actix-cors"
repository = "https://github.com/actix/actix-extras"
license = "MIT OR Apache-2.0"
edition = "2018"
@ -19,14 +17,14 @@ name = "actix_cors"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "4.0.0-beta.5", default-features = false }
actix-service = "2.0.0-beta.5"
actix-web = { version = "4.0.0-beta.8", default-features = false }
actix-service = "2.0.0"
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false }
log = "0.4"
once_cell = "1"
tinyvec = { version = "1", features = ["alloc"] }
smallvec = "1.6"
[dev-dependencies]
actix-rt = "2"

View File

@ -9,7 +9,7 @@ use actix_web::{
use futures_util::future::{self, Ready};
use log::error;
use once_cell::sync::Lazy;
use tinyvec::tiny_vec;
use smallvec::smallvec;
use crate::{AllOrSome, CorsError, CorsMiddleware, Inner, OriginFn};
@ -82,7 +82,7 @@ impl Cors {
pub fn permissive() -> Self {
let inner = Inner {
allowed_origins: AllOrSome::All,
allowed_origins_fns: tiny_vec![],
allowed_origins_fns: smallvec![],
allowed_methods: ALL_METHODS_SET.clone(),
allowed_methods_baked: None,
@ -458,7 +458,7 @@ impl Default for Cors {
fn default() -> Cors {
let inner = Inner {
allowed_origins: AllOrSome::Some(HashSet::with_capacity(8)),
allowed_origins_fns: tiny_vec![],
allowed_origins_fns: smallvec![],
allowed_methods: HashSet::with_capacity(8),
allowed_methods_baked: None,
@ -483,13 +483,12 @@ impl Default for Cors {
}
}
impl<S, B> Transform<S, ServiceRequest> for Cors
impl<S> Transform<S, ServiceRequest> for Cors
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Transform = CorsMiddleware<S>;

View File

@ -9,7 +9,7 @@ use actix_web::{
},
};
use once_cell::sync::Lazy;
use tinyvec::TinyVec;
use smallvec::SmallVec;
use crate::{AllOrSome, CorsError};
@ -42,7 +42,7 @@ fn header_value_try_into_method(hdr: &HeaderValue) -> Option<Method> {
#[derive(Debug, Clone)]
pub(crate) struct Inner {
pub(crate) allowed_origins: AllOrSome<HashSet<HeaderValue>>,
pub(crate) allowed_origins_fns: TinyVec<[OriginFn; 4]>,
pub(crate) allowed_origins_fns: SmallVec<[OriginFn; 4]>,
pub(crate) allowed_methods: HashSet<Method>,
pub(crate) allowed_methods_baked: Option<HeaderValue>,

View File

@ -1,6 +1,7 @@
use std::{convert::TryInto, rc::Rc};
use std::{convert::TryInto, error::Error as StdError, rc::Rc};
use actix_web::{
body::{AnyBody, MessageBody},
dev::{Service, ServiceRequest, ServiceResponse},
error::{Error, Result},
http::{
@ -9,7 +10,9 @@ use actix_web::{
},
HttpResponse,
};
use futures_util::future::{ok, Either, FutureExt as _, LocalBoxFuture, Ready};
use futures_util::future::{
ok, Either, FutureExt as _, LocalBoxFuture, Ready, TryFutureExt as _,
};
use log::debug;
use crate::Inner;
@ -26,7 +29,7 @@ pub struct CorsMiddleware<S> {
}
impl<S> CorsMiddleware<S> {
fn handle_preflight<B>(inner: &Inner, req: ServiceRequest) -> ServiceResponse<B> {
fn handle_preflight(inner: &Inner, req: ServiceRequest) -> ServiceResponse {
if let Err(err) = inner
.validate_origin(req.head())
.and_then(|_| inner.validate_allowed_method(req.head()))
@ -69,7 +72,6 @@ impl<S> CorsMiddleware<S> {
}
let res = res.finish();
let res = res.into_body();
req.into_response(res)
}
@ -112,20 +114,21 @@ impl<S> CorsMiddleware<S> {
}
}
type CorsMiddlewareServiceFuture<B> = Either<
Ready<Result<ServiceResponse<B>, Error>>,
LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>,
type CorsMiddlewareServiceFuture = Either<
Ready<Result<ServiceResponse, Error>>,
LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
>;
impl<S, B> Service<ServiceRequest> for CorsMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = Error;
type Future = CorsMiddlewareServiceFuture<B>;
type Future = CorsMiddlewareServiceFuture;
actix_service::forward_ready!(service);
@ -158,6 +161,7 @@ where
res
}
}
.map_ok(|res| res.map_body(|_, body| AnyBody::from_message(body)))
.boxed_local();
Either::Right(res)

View File

@ -3,10 +3,9 @@ name = "actix-identity"
version = "0.4.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Identity service for Actix web"
readme = "README.md"
keywords = ["actix", "auth", "identity", "web", "security"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
repository = "https://github.com/actix/actix-extras"
license = "MIT OR Apache-2.0"
edition = "2018"
@ -15,8 +14,8 @@ name = "actix_identity"
path = "src/lib.rs"
[dependencies]
actix-service = "2.0.0-beta.5"
actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies", "secure-cookies"] }
actix-service = "2.0.0"
actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies", "secure-cookies"] }
futures-util = { version = "0.3.7", default-features = false }
serde = "1.0"
serde_json = "1.0"

View File

@ -69,16 +69,17 @@ impl CookieIdentityInner {
value: Option<CookieValue>,
) -> Result<()> {
let add_cookie = value.is_some();
let val = value.map(|val| {
if !self.legacy_supported() {
serde_json::to_string(&val)
} else {
Ok(val.identity)
}
});
let val = value
.map(|val| {
if !self.legacy_supported() {
serde_json::to_string(&val)
} else {
Ok(val.identity)
}
})
.transpose()?;
let mut cookie =
Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
let mut cookie = Cookie::new(self.name.clone(), val.unwrap_or_default());
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
@ -108,10 +109,10 @@ impl CookieIdentityInner {
};
if add_cookie {
jar.private(&key).add(cookie);
jar.private_mut(&key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&key).remove(cookie);
jar.private_mut(&key).remove(cookie);
}
for cookie in jar.delta() {
@ -128,17 +129,19 @@ impl CookieIdentityInner {
jar.add_original(cookie.clone());
let res = if self.legacy_supported() {
jar.private(&self.key).get(&self.name).map(|n| CookieValue {
identity: n.value().to_string(),
login_timestamp: None,
visit_timestamp: None,
})
jar.private_mut(&self.key)
.get(&self.name)
.map(|n| CookieValue {
identity: n.value().to_string(),
login_timestamp: None,
visit_timestamp: None,
})
} else {
None
};
res.or_else(|| {
jar.private(&self.key_v2)
jar.private_mut(&self.key_v2)
.get(&self.name)
.and_then(|c| self.parse(c))
})
@ -391,7 +394,7 @@ mod tests {
.copied()
.collect();
jar.private(&Key::derive_from(&key)).add(Cookie::new(
jar.private_mut(&Key::derive_from(&key)).add(Cookie::new(
COOKIE_NAME,
serde_json::to_string(&CookieValue {
identity: identity.to_string(),
@ -575,7 +578,7 @@ mod tests {
fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
let mut jar = CookieJar::new();
jar.private(&Key::derive_from(&COOKIE_KEY_MASTER))
jar.private_mut(&Key::derive_from(&COOKIE_KEY_MASTER))
.add(Cookie::new(COOKIE_NAME, identity));
jar.get(COOKIE_NAME).unwrap().clone()
}
@ -592,7 +595,7 @@ mod tests {
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
}
let cookie = cookies
.private(&Key::derive_from(&COOKIE_KEY_MASTER))
.private_mut(&Key::derive_from(&COOKIE_KEY_MASTER))
.get(COOKIE_NAME)
.unwrap();
assert_eq!(cookie.value(), identity);

View File

@ -1,10 +1,13 @@
use std::rc::Rc;
use std::{error::Error as StdError, rc::Rc};
use actix_web::{
body::{AnyBody, MessageBody},
dev::{Service, ServiceRequest, ServiceResponse, Transform},
Error, HttpMessage, Result,
};
use futures_util::future::{ready, FutureExt, LocalBoxFuture, Ready};
use futures_util::future::{
ready, FutureExt as _, LocalBoxFuture, Ready, TryFutureExt as _,
};
use crate::{identity::IdentityItem, IdentityPolicy};
@ -41,9 +44,10 @@ where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
T: IdentityPolicy,
B: 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Transform = IdentityServiceMiddleware<S, T>;
@ -73,12 +77,13 @@ impl<S, T> Clone for IdentityServiceMiddleware<S, T> {
impl<S, T, B> Service<ServiceRequest> for IdentityServiceMiddleware<S, T>
where
B: 'static,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
T: IdentityPolicy,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
@ -100,16 +105,19 @@ where
if let Some(id) = id {
match backend.to_response(id.id, id.changed, &mut res).await {
Ok(_) => Ok(res),
Ok(_) => {
Ok(res.map_body(|_, body| AnyBody::from_message(body)))
}
Err(e) => Ok(res.error_response(e)),
}
} else {
Ok(res)
Ok(res.map_body(|_, body| AnyBody::from_message(body)))
}
}
Err(err) => Ok(req.error_response(err)),
}
}
.map_ok(|res| res.map_body(|_, body| AnyBody::from_message(body)))
.boxed_local()
}
}

View File

@ -7,10 +7,9 @@ authors = [
"Yuki Okushi <huyuumi.dev@gmail.com>"
]
description = "Protobuf support for Actix web"
readme = "README.md"
keywords = ["actix", "protobuf", "protocol", "rpc"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
repository = "https://github.com/actix/actix-extras"
license = "MIT OR Apache-2.0"
exclude = [".cargo/config", "/examples/**"]
@ -20,7 +19,7 @@ path = "src/lib.rs"
[dependencies]
actix-rt = "2"
actix-web = { version = "4.0.0-beta.5", default_features = false }
actix-web = { version = "4.0.0-beta.8", default_features = false }
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false }
prost = "0.7"

View File

@ -8,7 +8,7 @@ authors = [
]
[dependencies]
actix-web = "4.0.0-beta.5"
actix-web = "4.0.0-beta.8"
actix-protobuf = { path = "../../" }
env_logger = "0.8"

View File

@ -1,39 +1,49 @@
#![deny(rust_2018_idioms)]
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
use std::{
fmt,
future::Future,
ops::{Deref, DerefMut},
pin::Pin,
task::{self, Poll},
};
use actix_web::{
dev::Payload,
error::PayloadError,
http::header::{CONTENT_LENGTH, CONTENT_TYPE},
web::BytesMut,
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder,
Responder, ResponseError,
};
use derive_more::Display;
use std::fmt;
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::task;
use std::task::Poll;
use prost::DecodeError as ProtoBufDecodeError;
use prost::EncodeError as ProtoBufEncodeError;
use prost::Message;
use actix_web::dev::{HttpResponseBuilder, Payload};
use actix_web::error::{Error, PayloadError, ResponseError};
use actix_web::http::header::{CONTENT_LENGTH, CONTENT_TYPE};
use actix_web::web::BytesMut;
use actix_web::{FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures_util::future::{FutureExt, LocalBoxFuture};
use futures_util::StreamExt;
use futures_util::{
future::{FutureExt as _, LocalBoxFuture},
stream::StreamExt as _,
};
use prost::{
DecodeError as ProtoBufDecodeError, EncodeError as ProtoBufEncodeError, Message,
};
#[derive(Debug, Display)]
pub enum ProtoBufPayloadError {
/// Payload size is bigger than 256k
#[display(fmt = "Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[display(fmt = "Content type error")]
ContentType,
/// Serialize error
#[display(fmt = "ProtoBuf serialize error: {}", _0)]
Serialize(ProtoBufEncodeError),
/// Deserialize error
#[display(fmt = "ProtoBuf deserialize error: {}", _0)]
Deserialize(ProtoBufDecodeError),
/// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)]
Payload(PayloadError),

View File

@ -4,11 +4,9 @@ version = "0.10.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Redis integration for Actix and session store for Actix Web"
license = "MIT OR Apache-2.0"
readme = "README.md"
keywords = ["actix", "redis", "async", "session"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
documentation = "https://docs.rs/actix-redis/"
repository = "https://github.com/actix/actix-extras"
categories = ["network-programming", "asynchronous"]
exclude = [".cargo/config"]
edition = "2018"
@ -31,9 +29,9 @@ web = [
]
[dependencies]
actix = { version = "0.11.0", default-features = false }
actix = { version = "0.12.0", default-features = false }
actix-rt = { version = "2.1", default-features = false }
actix-service = "2.0.0-beta.5"
actix-service = "2.0.0"
actix-tls = { version = "3.0.0-beta.5", default-features = false, features = ["connect"] }
log = "0.4.6"
@ -47,15 +45,23 @@ tokio = { version = "1", features = ["sync"] }
tokio-util = "0.6.1"
# actix-session
actix-web = { version = "4.0.0-beta.5", default_features = false, optional = true }
actix-web = { version = "4.0.0-beta.8", default_features = false, optional = true }
actix-session = { version = "0.5.0-beta.1", optional = true }
rand = { version = "0.8.0", optional = true }
serde = { version = "1.0.101", optional = true }
serde_json = { version = "1.0.40", optional = true }
[dev-dependencies]
actix-test = "0.1.0-beta.1"
actix-test = "0.1.0-beta.3"
actix-http = "3.0.0-beta.5"
actix-rt = "2.1"
env_logger = "0.7"
serde_derive = "1.0"
env_logger = "0.8"
serde = { version = "1.0.101", features = ["derive"] }
[[example]]
name = "basic"
required-features = ["web"]
[[example]]
name = "authentication"
required-features = ["web"]

View File

@ -1,7 +1,8 @@
use actix_redis::RedisSession;
use actix_session::Session;
use actix_web::{
cookie, middleware, web, App, Error, HttpResponse, HttpServer, Responder,
cookie, error::InternalError, middleware, web, App, Error, HttpResponse, HttpServer,
Responder,
};
use serde::{Deserialize, Serialize};
@ -49,12 +50,12 @@ pub fn validate_session(session: &Session) -> Result<i64, HttpResponse> {
async fn login(
credentials: web::Json<Credentials>,
session: Session,
) -> Result<impl Responder, HttpResponse> {
) -> Result<impl Responder, Error> {
let credentials = credentials.into_inner();
match User::authenticate(credentials) {
Ok(user) => session.insert("user_id", user.id).unwrap(),
Err(_) => return Err(HttpResponse::Unauthorized().json("Unauthorized")),
Err(err) => return Err(InternalError::from_response("", err).into()),
};
Ok("Welcome!")
@ -63,7 +64,7 @@ async fn login(
/// some protected resource
async fn secret(session: Session) -> Result<impl Responder, Error> {
// only allow access to this resource if the user has an active session
validate_session(&session)?;
validate_session(&session).map_err(|err| InternalError::from_response("", err))?;
Ok("secret revealed")
}

View File

@ -3,14 +3,16 @@ use std::{collections::HashMap, iter, rc::Rc};
use actix::prelude::*;
use actix_service::{Service, Transform};
use actix_session::{Session, SessionStatus};
use actix_web::cookie::{Cookie, CookieJar, Key, SameSite};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::header::{self, HeaderValue};
use actix_web::{error, Error, HttpMessage};
use actix_web::{
cookie::{Cookie, CookieJar, Key, SameSite},
dev::{ServiceRequest, ServiceResponse},
error,
http::header::{self, HeaderValue},
Error,
};
use futures_core::future::LocalBoxFuture;
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
use redis_async::resp::RespValue;
use redis_async::resp_array;
use redis_async::{resp::RespValue, resp_array};
use time::{self, Duration, OffsetDateTime};
use crate::redis::{Command, RedisActor};
@ -311,7 +313,7 @@ impl Inner {
// set cookie
let mut jar = CookieJar::new();
jar.signed(&self.key).add(cookie);
jar.signed_mut(&self.key).add(cookie);
(value, Some(jar))
};
@ -321,7 +323,7 @@ impl Inner {
let state: HashMap<_, _> = state.collect();
let body = match serde_json::to_string(&state) {
Err(e) => return Err(e.into()),
Err(err) => return Err(err.into()),
Ok(body) => body,
};
@ -442,12 +444,15 @@ mod test {
async fn logout(session: Session) -> Result<HttpResponse> {
let id: Option<String> = session.get("user_id")?;
if let Some(x) = id {
let body = if let Some(x) = id {
session.purge();
Ok(format!("Logged out: {}", x).into())
format!("Logged out: {}", x)
} else {
Ok("Could not log out anonymous user".into())
}
"Could not log out anonymous user".to_owned()
};
Ok(HttpResponse::Ok().body(body))
}
#[actix_rt::test]
@ -648,7 +653,10 @@ mod test {
.unwrap();
assert_ne!(
OffsetDateTime::now_utc().year(),
cookie_4.expires().map(|t| t.year()).unwrap()
cookie_4
.expires()
.map(|t| t.datetime().expect("Expiration is a datetime").year())
.unwrap()
);
// Step 10: GET index, including session cookie #2 in request

View File

@ -3,11 +3,9 @@ name = "actix-session"
version = "0.5.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Sessions for Actix web"
readme = "README.md"
keywords = ["http", "web", "framework", "async", "session"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras"
documentation = "https://docs.rs/actix-session/"
license = "MIT OR Apache-2.0"
edition = "2018"
@ -20,8 +18,8 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"]
[dependencies]
actix-web = { version = "4.0.0-beta.5", default_features = false, features = ["cookies"] }
actix-service = "2.0.0-beta.5"
actix-web = { version = "4.0.0-beta.8", default_features = false, features = ["cookies"] }
actix-service = "2.0.0"
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false }

View File

@ -1,14 +1,16 @@
//! Cookie based sessions. See docs for [`CookieSession`].
use std::{collections::HashMap, rc::Rc};
use std::{collections::HashMap, error::Error as StdError, rc::Rc};
use actix_service::{Service, Transform};
use actix_web::cookie::{Cookie, CookieJar, Key, SameSite};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::{header::SET_COOKIE, HeaderValue};
use actix_web::{Error, HttpMessage, ResponseError};
use actix_web::{
body::{AnyBody, MessageBody},
cookie::{Cookie, CookieJar, Key, SameSite},
dev::{Service, ServiceRequest, ServiceResponse, Transform},
http::{header::SET_COOKIE, HeaderValue},
Error, ResponseError,
};
use derive_more::Display;
use futures_util::future::{ok, LocalBoxFuture, Ready};
use futures_util::future::{ok, FutureExt as _, LocalBoxFuture, Ready};
use serde_json::error::Error as JsonError;
use time::{Duration, OffsetDateTime};
@ -106,8 +108,8 @@ impl CookieSessionInner {
let mut jar = CookieJar::new();
match self.security {
CookieSecurity::Signed => jar.signed(&self.key).add(cookie),
CookieSecurity::Private => jar.private(&self.key).add(cookie),
CookieSecurity::Signed => jar.signed_mut(&self.key).add(cookie),
CookieSecurity::Private => jar.private_mut(&self.key).add(cookie),
}
for cookie in jar.delta() {
@ -292,13 +294,15 @@ impl CookieSession {
}
}
impl<S, B: 'static> Transform<S, ServiceRequest> for CookieSession
impl<S, B> Transform<S, ServiceRequest> for CookieSession
where
S: Service<ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = S::Error;
type InitError = ();
type Transform = CookieSessionMiddleware<S>;
@ -318,13 +322,15 @@ pub struct CookieSessionMiddleware<S> {
inner: Rc<CookieSessionInner>,
}
impl<S, B: 'static> Service<ServiceRequest> for CookieSessionMiddleware<S>
impl<S, B> Service<ServiceRequest> for CookieSessionMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = S::Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
@ -343,36 +349,40 @@ where
let fut = self.service.call(req);
Box::pin(async move {
async move {
let mut res = fut.await?;
let res = match Session::get_changes(&mut res) {
let result = match Session::get_changes(&mut res) {
(SessionStatus::Changed, state) | (SessionStatus::Renewed, state) => {
res.checked_expr(|res| inner.set_cookie(res, state))
inner.set_cookie(&mut res, state)
}
(SessionStatus::Unchanged, state) if prolong_expiration => {
res.checked_expr(|res| inner.set_cookie(res, state))
inner.set_cookie(&mut res, state)
}
// set a new session cookie upon first request (new client)
(SessionStatus::Unchanged, _) => {
if is_new {
let state: HashMap<String, String> = HashMap::new();
res.checked_expr(|res| inner.set_cookie(res, state.into_iter()))
inner.set_cookie(&mut res, state.into_iter())
} else {
res
Ok(())
}
}
(SessionStatus::Purged, _) => {
let _ = inner.remove_cookie(&mut res);
res
Ok(())
}
};
Ok(res)
})
match result {
Ok(()) => Ok(res.map_body(|_, body| AnyBody::from_message(body))),
Err(error) => Ok(res.error_response(error)),
}
}
.boxed_local()
}
}
@ -533,7 +543,9 @@ mod tests {
.find(|c| c.name() == "actix-session")
.expect("Cookie is set")
.expires()
.expect("Expiration is set");
.expect("Expiration is set")
.datetime()
.expect("Expiration is a datetime");
actix_rt::time::sleep(std::time::Duration::from_secs(1)).await;
@ -545,7 +557,9 @@ mod tests {
.find(|c| c.name() == "actix-session")
.expect("Cookie is set")
.expires()
.expect("Expiration is set");
.expect("Expiration is set")
.datetime()
.expect("Expiration is a datetime");
assert!(expires_2 - expires_1 >= Duration::seconds(1));
}

View File

@ -6,11 +6,9 @@ authors = [
"Yuki Okushi <huyuumi.dev@gmail.com>",
]
description = "HTTP authentication schemes for Actix web"
readme = "README.md"
keywords = ["http", "web", "framework", "authentication", "security"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
documentation = "https://docs.rs/actix-web-httpauth/"
repository = "https://github.com/actix/actix-extras"
categories = ["web-programming::http-server"]
license = "MIT OR Apache-2.0"
edition = "2018"
@ -20,8 +18,8 @@ name = "actix_web_httpauth"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "4.0.0-beta.5", default_features = false }
actix-service = "2.0.0-beta.5"
actix-web = { version = "4.0.0-beta.8", default_features = false }
actix-service = "2.0.0"
base64 = "0.13"
futures-util = { version = "0.3.7", default-features = false }

View File

@ -81,7 +81,7 @@ impl AuthExtractorConfig for Config {
///
/// fn main() {
/// let app = App::new()
/// .data(Config::default().realm("Restricted area"))
/// .app_data(Config::default().realm("Restricted area"))
/// .service(web::resource("/index.html").route(web::get().to(index)));
/// }
/// ```

View File

@ -84,7 +84,7 @@ impl AuthExtractorConfig for Config {
///
/// fn main() {
/// let app = App::new()
/// .data(
/// .app_data(
/// Config::default()
/// .realm("Restricted area")
/// .scope("email photo"),

View File

@ -1,8 +1,12 @@
//! HTTP Authentication middleware.
use std::{future::Future, marker::PhantomData, pin::Pin, rc::Rc, sync::Arc};
use std::{
error::Error as StdError, future::Future, marker::PhantomData, pin::Pin, rc::Rc,
sync::Arc,
};
use actix_web::{
body::{AnyBody, MessageBody},
dev::{Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
@ -120,8 +124,10 @@ where
F: Fn(ServiceRequest, T) -> O + 'static,
O: Future<Output = Result<ServiceRequest, Error>> + 'static,
T: AuthExtractor + 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = Error;
type Transform = AuthenticationMiddleware<S, F, T>;
type InitError = ();
@ -153,10 +159,12 @@ where
F: Fn(ServiceRequest, T) -> O + 'static,
O: Future<Output = Result<ServiceRequest, Error>> + 'static,
T: AuthExtractor + 'static,
B: MessageBody + 'static,
B::Error: StdError,
{
type Response = ServiceResponse<B>;
type Response = ServiceResponse;
type Error = S::Error;
type Future = LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>;
type Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
actix_service::forward_ready!(service);
@ -177,7 +185,10 @@ where
// middleware to do their thing (eg. cors adding headers)
let req = process_fn(req, credentials).await?;
service.call(req).await
service
.call(req)
.await
.map(|res| res.map_body(|_, body| AnyBody::from_message(body)))
}
.boxed_local()
}