mirror of
https://github.com/actix/actix-extras.git
synced 2025-04-21 17:46:49 +02:00
Compare commits
49 Commits
identity-v
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
c04cc19e73 | ||
|
6a13b3b182 | ||
|
d994912ac2 | ||
|
5f6f20cf37 | ||
|
5145924410 | ||
|
b20dec36ac | ||
|
f6e45d487b | ||
|
c53e198ea7 | ||
|
4d9984ee76 | ||
|
9a08090709 | ||
|
7d3348bb29 | ||
|
c0fa63af39 | ||
|
0b5e2b3647 | ||
|
b95595b9cd | ||
|
4b3f87e915 | ||
|
144c7f92b9 | ||
|
c71b9dd443 | ||
|
282d56e96b | ||
|
d514ad3af5 | ||
|
109e6a4793 | ||
|
bb0c7f21d9 | ||
|
3f7a479a76 | ||
|
fc4b656c3b | ||
|
0f35de7da1 | ||
|
8294fcc645 | ||
|
3de6b03711 | ||
|
64931189c7 | ||
|
265b213123 | ||
|
695369f02f | ||
|
87d9e51112 | ||
|
8c11d37dda | ||
|
d97b36652a | ||
|
98847b9279 | ||
|
cd1b77134e | ||
|
105932706d | ||
|
18f94fa8b5 | ||
|
66b82f0f30 | ||
|
d67abde5f3 | ||
|
3eafe7f5ce | ||
|
3b5f7ae68c | ||
|
036af488fd | ||
|
77406cbb71 | ||
|
2ede588693 | ||
|
21680e0ebe | ||
|
370f9d3033 | ||
|
8f4fb348b3 | ||
|
ff4b173716 | ||
|
49aacfce9f | ||
|
dd20ebb6cb |
8
.github/workflows/ci-post-merge.yml
vendored
8
.github/workflows/ci-post-merge.yml
vendored
@ -31,12 +31,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install cargo-hack, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
|
||||
@ -81,12 +81,12 @@ jobs:
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
|
||||
|
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@ -44,19 +44,18 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean, just
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
tool: cargo-hack,cargo-ci-cache-clean,just
|
||||
|
||||
# - name: workaround MSRV issues
|
||||
# if: matrix.version.name == 'msrv'
|
||||
# run: |
|
||||
# cargo update -p=time:0.3.20 --precise=0.3.16
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-min
|
||||
@ -102,19 +101,18 @@ jobs:
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install cargo-hack, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
- name: Install cargo-hack, cargo-ci-cache-clean, just
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
tool: cargo-hack,cargo-ci-cache-clean,just
|
||||
|
||||
# - name: workaround MSRV issues
|
||||
# if: matrix.version.name == 'msrv'
|
||||
# run: |
|
||||
# cargo update -p=time:0.3.20 --precise=0.3.16
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-min
|
||||
@ -139,12 +137,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: just
|
||||
|
||||
|
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@ -25,13 +25,13 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install just, cargo-llvm-cov, cargo-nextest
|
||||
uses: taiki-e/install-action@v2.42.37
|
||||
uses: taiki-e/install-action@v2.49.42
|
||||
with:
|
||||
tool: just,cargo-llvm-cov,cargo-nextest
|
||||
|
||||
@ -39,7 +39,7 @@ jobs:
|
||||
run: just test-coverage-codecov
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
|
33
.github/workflows/lint.yml
vendored
33
.github/workflows/lint.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
@ -46,32 +46,3 @@ jobs:
|
||||
clippy_flags: >-
|
||||
--workspace --all-features --tests --examples --bins --
|
||||
-A unknown_lints -D clippy::todo -D clippy::dbg_macro
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout ${{ github.base_ref }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: checkout ${{ github.head_ref }}
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/cache-cargo-install-action@v2.0.1
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
- name: generate API diff
|
||||
run: |
|
||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||
|
||||
cargo public-api --manifest-path "$f" --all-features diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} >> /tmp/diff.txt
|
||||
done
|
||||
cat /tmp/diff.txt
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
||||
guide/build/
|
||||
/gh-pages
|
||||
|
3292
Cargo.lock
generated
Normal file
3292
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
README.md
20
README.md
@ -33,8 +33,7 @@ These crates are provided by the community.
|
||||
| Crate | | |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| [actix-web-lab] | [][actix-web-lab] [](https://deps.rs/crate/actix-web-lab) | Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. |
|
||||
| [actix-multipart-extract] | [][actix-multipart-extract] [](https://deps.rs/crate/actix-multipart-extract) | Better multipart form support for Actix Web. |
|
||||
| [actix-form-data] | [][actix-form-data] [](https://deps.rs/crate/actix-form-data) | Multipart form data from actix multipart streams |
|
||||
| [actix-form-data] | [][actix-form-data] [](https://deps.rs/crate/actix-form-data) | Multipart form data from actix multipart streams. |
|
||||
| [actix-governor] | [][actix-governor] [](https://deps.rs/crate/actix-governor) | Rate-limiting backed by governor. |
|
||||
| [actix-casbin] | [][actix-casbin] [](https://deps.rs/crate/actix-casbin) | Authorization library that supports access control models like ACL, RBAC & ABAC. |
|
||||
| [actix-ip-filter] | [][actix-ip-filter] [](https://deps.rs/crate/actix-ip-filter) | IP address filter. Supports glob patterns. |
|
||||
@ -45,10 +44,13 @@ These crates are provided by the community.
|
||||
| [awmp] | [][awmp] [](https://deps.rs/crate/awmp) | An easy to use wrapper around multipart fields for Actix Web. |
|
||||
| [tracing-actix-web] | [][tracing-actix-web] [](https://deps.rs/crate/tracing-actix-web) | A middleware to collect telemetry data from applications built on top of the Actix Web framework. |
|
||||
| [actix-hash] | [][actix-hash] [](https://deps.rs/crate/actix-hash) | Hashing utilities for Actix Web. |
|
||||
| [actix-bincode] |  [](https://deps.rs/crate/actix-bincode) | Bincode payload extractor for Actix Web |
|
||||
| [sentinel-actix] |  [](https://deps.rs/crate/sentinel-actix) | General and flexible protection for Actix Web |
|
||||
| [actix-bincode] |  [](https://deps.rs/crate/actix-bincode) | Bincode payload extractor for Actix Web. |
|
||||
| [sentinel-actix] |  [](https://deps.rs/crate/sentinel-actix) | General and flexible protection for Actix Web. |
|
||||
| [actix-telepathy] |  [](https://deps.rs/crate/actix-telepathy) | Build distributed applications with `RemoteActors` and `RemoteMessages`. |
|
||||
| [apistos] |  [](https://deps.rs/crate/apistos) | Automatic OpenAPI v3 documentation for Actix Web |
|
||||
| [apistos] |  [](https://deps.rs/crate/apistos) | Automatic OpenAPI v3 documentation for Actix Web. |
|
||||
| [actix-web-validation] |  [](https://deps.rs/crate/actix-web-validation) | Request validation for Actix Web. |
|
||||
| [actix-jwt-cookies] |  [](https://deps.rs/repo/github/Necoo33/actix-jwt-cookies?path=%2F) | Store your data in encrypted cookies and get it elegantly. |
|
||||
| [actix-ws-broadcaster] |  [](https://deps.rs/repo/github/Necoo33/actix-ws-broadcaster?path=%2F) | A broadcaster library for actix-ws that includes grouping and conditional broadcasting. |
|
||||
|
||||
To add a crate to this list, submit a pull request.
|
||||
|
||||
@ -80,5 +82,9 @@ To add a crate to this list, submit a pull request.
|
||||
[actix-hash]: https://crates.io/crates/actix-hash
|
||||
[actix-bincode]: https://crates.io/crates/actix-bincode
|
||||
[sentinel-actix]: https://crates.io/crates/sentinel-actix
|
||||
[actix-telepathy]: https://github.com/wenig/actix-telepathy
|
||||
[apistos]: https://github.com/netwo-io/apistos
|
||||
[actix-telepathy]: https://crates.io/crates/actix-telepathy
|
||||
[actix-web-validation]: https://crates.io/crates/actix-web-validation
|
||||
[actix-telepathy]: https://crates.io/crates/actix-telepathy
|
||||
[apistos]: https://crates.io/crates/apistos
|
||||
[actix-jwt-cookies]: https://crates.io/crates/actix-jwt-cookies
|
||||
[actix-ws-broadcaster]: https://crates.io/crates/actix-ws-broadcaster
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.7.1
|
||||
|
||||
- Implement `PartialEq` for `Cors` allowing for better testing.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
- `Cors` is now marked `#[must_use]`.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-cors"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -24,7 +24,7 @@ draft-private-network-access = []
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
derive_more = { version = "1", features = ["display", "error"] }
|
||||
derive_more = { version = "2", features = ["display", "error"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
log = "0.4"
|
||||
once_cell = "1"
|
||||
|
@ -3,11 +3,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-cors)
|
||||
[](https://docs.rs/actix-cors/0.7.0)
|
||||
[](https://docs.rs/actix-cors/0.7.1)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-cors/0.7.0)
|
||||
[](https://deps.rs/crate/actix-cors/0.7.1)
|
||||
[](https://crates.io/crates/actix-cors)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -608,6 +608,19 @@ where
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
impl PartialEq for Cors {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
// Because of the cors-function, checking if the content is equal implies that the errors are equal
|
||||
//
|
||||
// Proof by contradiction:
|
||||
// Lets assume that the inner values are equal, but the error values are not.
|
||||
// This means there had been an error, which has been fixed.
|
||||
// This cannot happen as the first call to set the invalid value means that further usages of the cors-function will reject other input.
|
||||
// => inner has to be in a different state
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::convert::Infallible;
|
||||
@ -679,4 +692,11 @@ mod test {
|
||||
|
||||
Cors::default().new_transform(srv).await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_eq() {
|
||||
assert_eq!(Cors::default(), Cors::default());
|
||||
assert_ne!(Cors::default().send_wildcard(), Cors::default());
|
||||
assert_ne!(Cors::default(), Cors::permissive());
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,12 @@ impl Default for OriginFn {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OriginFn {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.boxed_fn, &other.boxed_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for OriginFn {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("origin_fn")
|
||||
@ -40,7 +46,7 @@ pub(crate) fn header_value_try_into_method(hdr: &HeaderValue) -> Option<Method>
|
||||
.and_then(|meth| Method::try_from(meth).ok())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct Inner {
|
||||
pub(crate) allowed_origins: AllOrSome<HashSet<HeaderValue>>,
|
||||
pub(crate) allowed_origins_fns: SmallVec<[OriginFn; 4]>,
|
||||
|
@ -23,7 +23,7 @@ actix-session = "0.10"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
|
||||
|
||||
derive_more = { version = "1", features = ["display", "error", "from"] }
|
||||
derive_more = { version = "2", features = ["display", "error", "from"] }
|
||||
futures-core = "0.3.17"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
@ -83,8 +83,10 @@ async fn login(request: HttpRequest) -> impl Responder {
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
async fn logout(user: Identity) -> impl Responder {
|
||||
user.logout();
|
||||
async fn logout(user: Option<Identity>) -> impl Responder {
|
||||
if let Some(user) = user {
|
||||
user.logout();
|
||||
}
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
```
|
||||
|
@ -13,10 +13,10 @@
|
||||
//! http -v --session=identity GET localhost:8080/
|
||||
//! ```
|
||||
|
||||
use std::io;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use actix_identity::{Identity, IdentityMiddleware};
|
||||
use actix_session::{storage::CookieSessionStore, SessionMiddleware};
|
||||
use actix_session::{config::PersistentSession, storage::CookieSessionStore, SessionMiddleware};
|
||||
use actix_web::{
|
||||
cookie::Key, get, middleware::Logger, post, App, HttpMessage, HttpRequest, HttpResponse,
|
||||
HttpServer, Responder,
|
||||
@ -28,16 +28,25 @@ async fn main() -> io::Result<()> {
|
||||
|
||||
let secret_key = Key::generate();
|
||||
|
||||
let expiration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
HttpServer::new(move || {
|
||||
let session_mw =
|
||||
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
|
||||
// disable secure cookie for local testing
|
||||
.cookie_secure(false)
|
||||
// Set a ttl for the cookie if the identity should live longer than the user session
|
||||
.session_lifecycle(
|
||||
PersistentSession::default().session_ttl(expiration.try_into().unwrap()),
|
||||
)
|
||||
.build();
|
||||
let identity_mw = IdentityMiddleware::builder()
|
||||
.visit_deadline(Some(expiration))
|
||||
.build();
|
||||
|
||||
App::new()
|
||||
// Install the identity framework first.
|
||||
.wrap(IdentityMiddleware::default())
|
||||
.wrap(identity_mw)
|
||||
// The identity system is built on top of sessions. You must install the session
|
||||
// middleware to leverage `actix-identity`. The session middleware must be mounted
|
||||
// AFTER the identity middleware: `actix-web` invokes middleware in the OPPOSITE
|
||||
|
@ -20,7 +20,7 @@ impl IdentityExt for ServiceRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IdentityExt for GuardContext<'a> {
|
||||
impl IdentityExt for GuardContext<'_> {
|
||||
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
|
||||
Identity::extract(&self.req_data())
|
||||
}
|
||||
|
@ -74,8 +74,10 @@ async fn login(request: HttpRequest) -> impl Responder {
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
async fn logout(user: Identity) -> impl Responder {
|
||||
user.logout();
|
||||
async fn logout(user: Option<Identity>) -> impl Responder {
|
||||
if let Some(user) = user {
|
||||
user.logout();
|
||||
}
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
```
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Update `redis` dependency to `0.26`.
|
||||
- Update `redis` dependency to `0.29`.
|
||||
- Update `actix-session` dependency to `0.9`.
|
||||
|
||||
## 0.5.1
|
||||
|
@ -26,9 +26,9 @@ actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
|
||||
chrono = "0.4"
|
||||
derive_more = { version = "1", features = ["display", "error", "from"] }
|
||||
derive_more = { version = "2", features = ["display", "error", "from"] }
|
||||
log = "0.4"
|
||||
redis = { version = "0.26", default-features = false, features = ["tokio-comp"] }
|
||||
redis = { version = "0.29", default-features = false, features = ["tokio-comp"] }
|
||||
time = "0.3"
|
||||
|
||||
# session
|
||||
|
@ -16,7 +16,7 @@ impl Status {
|
||||
/// Constructs status limit status from parts.
|
||||
#[must_use]
|
||||
pub(crate) fn new(count: usize, limit: usize, reset_epoch_utc: usize) -> Self {
|
||||
let remaining = if count >= limit { 0 } else { limit - count };
|
||||
let remaining = limit.saturating_sub(count);
|
||||
|
||||
Status {
|
||||
limit,
|
||||
|
@ -19,7 +19,7 @@ all-features = true
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4", default-features = false }
|
||||
derive_more = { version = "1", features = ["display"] }
|
||||
derive_more = { version = "2", features = ["display"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
prost = { version = "0.13", default-features = false }
|
||||
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add `Session::contains_key` method.
|
||||
- Add `Session::update[_or]()` methods.
|
||||
- Update `redis` dependency to `0.29`.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
- Expose `storage::generate_session_key()` without needing to enable a crate feature.
|
||||
|
@ -31,15 +31,15 @@ actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
|
||||
|
||||
anyhow = "1"
|
||||
derive_more = { version = "1", features = ["display", "error", "from"] }
|
||||
rand = "0.8"
|
||||
derive_more = { version = "2", features = ["display", "error", "from"] }
|
||||
rand = "0.9"
|
||||
serde = { version = "1" }
|
||||
serde_json = { version = "1" }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# redis-session
|
||||
redis = { version = "0.26", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }
|
||||
deadpool-redis = { version = "0.16", optional = true }
|
||||
redis = { version = "0.29", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }
|
||||
deadpool-redis = { version = "0.20", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-session = { path = ".", features = ["cookie-session", "redis-session"] }
|
||||
|
@ -148,6 +148,7 @@ pub use self::{
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod test_helpers {
|
||||
use actix_web::cookie::Key;
|
||||
|
||||
|
@ -33,6 +33,9 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
/// session.insert("counter", 1)?;
|
||||
/// }
|
||||
///
|
||||
/// // or use the shorthand
|
||||
/// session.update_or("counter", 1, |count: i32| count + 1);
|
||||
///
|
||||
/// Ok("Welcome!")
|
||||
/// }
|
||||
/// # actix_web::web::to(index);
|
||||
@ -97,6 +100,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the session contains a value for the specified `key`.
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.0.borrow().state.contains_key(key)
|
||||
}
|
||||
|
||||
/// Get all raw key-value data from the session.
|
||||
///
|
||||
/// Note that values are JSON encoded.
|
||||
@ -114,7 +122,9 @@ impl Session {
|
||||
/// Any serializable value can be used and will be encoded as JSON in session data, hence why
|
||||
/// only a reference to the value is taken.
|
||||
///
|
||||
/// It returns an error if it fails to serialize `value` to JSON.
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if JSON serialization of `value` fails.
|
||||
pub fn insert<T: Serialize>(
|
||||
&self,
|
||||
key: impl Into<String>,
|
||||
@ -132,9 +142,8 @@ impl Session {
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to serialize the provided `{}` type instance as JSON in order to \
|
||||
attach as session data to the `{}` key",
|
||||
attach as session data to the `{key}` key",
|
||||
std::any::type_name::<T>(),
|
||||
&key
|
||||
)
|
||||
})
|
||||
.map_err(SessionInsertError)?;
|
||||
@ -145,6 +154,83 @@ impl Session {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates a key-value pair into the session.
|
||||
///
|
||||
/// If the key exists then update it to the new value and place it back in. If the key does not
|
||||
/// exist it will not be updated.
|
||||
///
|
||||
/// Any serializable value can be used and will be encoded as JSON in the session data, hence
|
||||
/// why only a reference to the value is taken.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if JSON serialization of the value fails.
|
||||
pub fn update<T: Serialize + DeserializeOwned, F>(
|
||||
&self,
|
||||
key: impl Into<String>,
|
||||
updater: F,
|
||||
) -> Result<(), SessionUpdateError>
|
||||
where
|
||||
F: FnOnce(T) -> T,
|
||||
{
|
||||
let mut inner = self.0.borrow_mut();
|
||||
let key_str = key.into();
|
||||
|
||||
if let Some(val_str) = inner.state.get(&key_str) {
|
||||
let value = serde_json::from_str(val_str)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to deserialize the JSON-encoded session data attached to key \
|
||||
`{key_str}` as a `{}` type",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
})
|
||||
.map_err(SessionUpdateError)?;
|
||||
|
||||
let val = serde_json::to_string(&updater(value))
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to serialize the provided `{}` type instance as JSON in order to \
|
||||
attach as session data to the `{key_str}` key",
|
||||
std::any::type_name::<T>(),
|
||||
)
|
||||
})
|
||||
.map_err(SessionUpdateError)?;
|
||||
|
||||
inner.state.insert(key_str, val);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates a key-value pair into the session, or inserts a default value.
|
||||
///
|
||||
/// If the key exists then update it to the new value and place it back in. If the key does not
|
||||
/// exist the default value will be inserted instead.
|
||||
///
|
||||
/// Any serializable value can be used and will be encoded as JSON in session data, hence why
|
||||
/// only a reference to the value is taken.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if JSON serialization of a value fails.
|
||||
pub fn update_or<T: Serialize + DeserializeOwned, F>(
|
||||
&self,
|
||||
key: &str,
|
||||
default_value: T,
|
||||
updater: F,
|
||||
) -> Result<(), SessionUpdateError>
|
||||
where
|
||||
F: FnOnce(T) -> T,
|
||||
{
|
||||
if self.contains_key(key) {
|
||||
self.update(key, updater)
|
||||
} else {
|
||||
self.insert(key, default_value)
|
||||
.map_err(|err| SessionUpdateError(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove value from the session.
|
||||
///
|
||||
/// If present, the JSON encoded value is returned.
|
||||
@ -319,3 +405,20 @@ impl ResponseError for SessionInsertError {
|
||||
HttpResponse::new(self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by [`Session::update`].
|
||||
#[derive(Debug, Display, From)]
|
||||
#[display("{_0}")]
|
||||
pub struct SessionUpdateError(anyhow::Error);
|
||||
|
||||
impl StdError for SessionUpdateError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
Some(self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for SessionUpdateError {
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
HttpResponse::new(self.status_code())
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ impl SessionExt for ServiceResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SessionExt for GuardContext<'a> {
|
||||
impl SessionExt for GuardContext<'_> {
|
||||
fn get_session(&self) -> Session {
|
||||
Session::get_session(&mut self.req_data_mut())
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ use crate::storage::{
|
||||
/// ```
|
||||
///
|
||||
/// # TLS support
|
||||
/// Add the `redis-rs-tls-session` or `redis-rs-tls-session-rustls` feature flag to enable TLS support. You can then establish a TLS
|
||||
/// Add the `redis-session-native-tls` or `redis-session-rustls` feature flag to enable TLS support. You can then establish a TLS
|
||||
/// connection to Redis using the `rediss://` URL scheme:
|
||||
///
|
||||
/// ```no_run
|
||||
@ -205,7 +205,6 @@ impl SessionStore for RedisSessionStore {
|
||||
let value: Option<String> = self
|
||||
.execute_command(redis::cmd("GET").arg(&[&cache_key]))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map_err(LoadError::Other)?;
|
||||
|
||||
match value {
|
||||
@ -240,7 +239,6 @@ impl SessionStore for RedisSessionStore {
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map_err(SaveError::Other)?;
|
||||
|
||||
Ok(session_key)
|
||||
@ -267,7 +265,6 @@ impl SessionStore for RedisSessionStore {
|
||||
&format!("{}", ttl.whole_seconds()),
|
||||
]))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map_err(UpdateError::Other)?;
|
||||
|
||||
match v {
|
||||
@ -318,7 +315,6 @@ impl SessionStore for RedisSessionStore {
|
||||
|
||||
self.execute_command::<()>(redis::cmd("DEL").arg(&[&cache_key]))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map_err(UpdateError::Other)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rand::distributions::{Alphanumeric, DistString as _};
|
||||
use rand::distr::{Alphanumeric, SampleString as _};
|
||||
|
||||
use crate::storage::SessionKey;
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::storage::SessionKey;
|
||||
/// [OWASP recommendations]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
|
||||
pub fn generate_session_key() -> SessionKey {
|
||||
Alphanumeric
|
||||
.sample_string(&mut rand::thread_rng(), 64)
|
||||
.sample_string(&mut rand::rng(), 64)
|
||||
.try_into()
|
||||
.expect("generated string should be within size range for a session key")
|
||||
}
|
||||
|
@ -69,6 +69,16 @@ async fn session_entries() {
|
||||
map.contains_key("test_num");
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn session_contains_key() {
|
||||
let req = test::TestRequest::default().to_srv_request();
|
||||
let session = req.get_session();
|
||||
session.insert("test_str", "val").unwrap();
|
||||
session.insert("test_str", 1).unwrap();
|
||||
assert!(session.contains_key("test_str"));
|
||||
assert!(!session.contains_key("test_num"));
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn insert_session_after_renew() {
|
||||
let session = test::TestRequest::default().to_srv_request().get_session();
|
||||
@ -83,6 +93,35 @@ async fn insert_session_after_renew() {
|
||||
assert_eq!(session.status(), SessionStatus::Renewed);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn update_session() {
|
||||
let session = test::TestRequest::default().to_srv_request().get_session();
|
||||
|
||||
session.update("test_val", |c: u32| c + 1).unwrap();
|
||||
assert_eq!(session.status(), SessionStatus::Unchanged);
|
||||
|
||||
session.insert("test_val", 0).unwrap();
|
||||
assert_eq!(session.status(), SessionStatus::Changed);
|
||||
|
||||
session.update("test_val", |c: u32| c + 1).unwrap();
|
||||
assert_eq!(session.get("test_val").unwrap(), Some(1));
|
||||
|
||||
session.update("test_val", |c: u32| c + 1).unwrap();
|
||||
assert_eq!(session.get("test_val").unwrap(), Some(2));
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn update_or_session() {
|
||||
let session = test::TestRequest::default().to_srv_request().get_session();
|
||||
|
||||
session.update_or("test_val", 1, |c: u32| c + 1).unwrap();
|
||||
assert_eq!(session.status(), SessionStatus::Changed);
|
||||
assert_eq!(session.get("test_val").unwrap(), Some(1));
|
||||
|
||||
session.update_or("test_val", 1, |c: u32| c + 1).unwrap();
|
||||
assert_eq!(session.get("test_val").unwrap(), Some(2));
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn remove_session_after_renew() {
|
||||
let session = test::TestRequest::default().to_srv_request().get_session();
|
||||
|
@ -22,8 +22,8 @@ openssl = ["dep:openssl", "actix-web/openssl"]
|
||||
actix-http = "3"
|
||||
actix-service = "2"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
derive_more = { version = "1", features = ["display", "error"] }
|
||||
once_cell = "1.13"
|
||||
derive_more = { version = "2", features = ["display", "error"] }
|
||||
once_cell = "1.21"
|
||||
openssl = { version = "0.10", features = ["v110"], optional = true }
|
||||
regex = "1.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
@ -43,7 +43,7 @@ impl<'de> de::Deserialize<'de> for Backlog {
|
||||
{
|
||||
struct BacklogVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for BacklogVisitor {
|
||||
impl de::Visitor<'_> for BacklogVisitor {
|
||||
type Value = Backlog;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -68,7 +68,7 @@ impl<'de> de::Deserialize<'de> for KeepAlive {
|
||||
{
|
||||
struct KeepAliveVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for KeepAliveVisitor {
|
||||
impl de::Visitor<'_> for KeepAliveVisitor {
|
||||
type Value = KeepAlive;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -40,7 +40,7 @@ impl<'de> de::Deserialize<'de> for MaxConnectionRate {
|
||||
{
|
||||
struct MaxConnectionRateVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for MaxConnectionRateVisitor {
|
||||
impl de::Visitor<'_> for MaxConnectionRateVisitor {
|
||||
type Value = MaxConnectionRate;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -40,7 +40,7 @@ impl<'de> de::Deserialize<'de> for MaxConnections {
|
||||
{
|
||||
struct MaxConnectionsVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for MaxConnectionsVisitor {
|
||||
impl de::Visitor<'_> for MaxConnectionsVisitor {
|
||||
type Value = MaxConnections;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -39,7 +39,7 @@ impl<'de> de::Deserialize<'de> for NumWorkers {
|
||||
{
|
||||
struct NumWorkersVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for NumWorkersVisitor {
|
||||
impl de::Visitor<'_> for NumWorkersVisitor {
|
||||
type Value = NumWorkers;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -71,7 +71,7 @@ impl<'de> de::Deserialize<'de> for Timeout {
|
||||
{
|
||||
struct TimeoutVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for TimeoutVisitor {
|
||||
impl de::Visitor<'_> for TimeoutVisitor {
|
||||
type Value = Timeout;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Ensure TCP connection is properly shut down when session is dropped.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- Add `AggregatedMessage[Stream]` types.
|
||||
|
@ -25,7 +25,7 @@ async fn ws(req: HttpRequest, body: web::Payload) -> actix_web::Result<impl Resp
|
||||
let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;
|
||||
|
||||
actix_web::rt::spawn(async move {
|
||||
while let Some(Ok(msg)) = msg_stream.next().await {
|
||||
while let Some(Ok(msg)) = msg_stream.recv().await {
|
||||
match msg {
|
||||
Message::Ping(bytes) => {
|
||||
if session.pong(&bytes).await.is_err() {
|
||||
|
@ -145,6 +145,10 @@ impl Stream for StreamingBody {
|
||||
return Poll::Ready(Some(Ok(mem::take(&mut this.buf).freeze())));
|
||||
}
|
||||
|
||||
if this.closing {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
17
justfile
17
justfile
@ -43,6 +43,20 @@ update-readmes:
|
||||
[group("test")]
|
||||
test:
|
||||
cargo {{ toolchain }} nextest run --workspace --all-features
|
||||
cargo {{ toolchain }} test --doc --workspace --all-features
|
||||
|
||||
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
||||
[private]
|
||||
downgrade-for-msrv:
|
||||
cargo update -p=native-tls --precise=0.2.13
|
||||
cargo update -p=litemap --precise=0.7.4
|
||||
cargo update -p=zerofrom --precise=0.1.5
|
||||
|
||||
# Test workspace using MSRV.
|
||||
[group("test")]
|
||||
test-msrv:
|
||||
@just downgrade-for-msrv
|
||||
@just toolchain={{ msrv_rustup }} test
|
||||
|
||||
# Test workspace code and docs.
|
||||
[group("test")]
|
||||
@ -71,6 +85,7 @@ test-docs:
|
||||
# Document crates in workspace.
|
||||
[group("docs")]
|
||||
doc *args: && doc-set-workspace-crates
|
||||
rm -f "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
|
||||
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --workspace --all-features {{ args }}
|
||||
|
||||
[group("docs")]
|
||||
@ -78,7 +93,7 @@ doc *args: && doc-set-workspace-crates
|
||||
doc-set-workspace-crates:
|
||||
#!/usr/bin/env bash
|
||||
(
|
||||
echo "window.ALL_CRATES ="
|
||||
echo "window.ALL_CRATES = "
|
||||
cargo metadata --format-version=1 \
|
||||
| jq '[.packages[] | select(.source == null) | .targets | map(select(.doc) | .name)] | flatten'
|
||||
echo ";"
|
||||
|
Loading…
x
Reference in New Issue
Block a user