1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-04-21 17:46:49 +02:00

Compare commits

...

310 Commits

Author SHA1 Message Date
dependabot[bot]
c04cc19e73
build(deps): bump anyhow from 1.0.97 to 1.0.98 (#526)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.97 to 1.0.98.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.97...1.0.98)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.98
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 15:19:05 +00:00
Rob Ede
6a13b3b182
chore: update deps 2025-04-09 21:52:16 +01:00
dependabot[bot]
d994912ac2
build(deps): bump openssl from 0.10.71 to 0.10.72 (#521)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.71 to 0.10.72.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.72
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-05 21:20:00 +00:00
dependabot[bot]
5f6f20cf37
build(deps): bump taiki-e/install-action from 2.49.9 to 2.49.42 (#520)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.9 to 2.49.42.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.49.9...v2.49.42)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 16:34:01 +00:00
dependabot[bot]
5145924410
build(deps): bump once_cell from 1.21.1 to 1.21.3 (#519)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.21.1 to 1.21.3.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.3)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 16:44:02 +00:00
dependabot[bot]
b20dec36ac
build(deps): bump time from 0.3.39 to 0.3.41 (#515)
Bumps [time](https://github.com/time-rs/time) from 0.3.39 to 0.3.41.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.39...v0.3.41)

---
updated-dependencies:
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:37:20 +00:00
dependabot[bot]
f6e45d487b
build(deps): bump reqwest from 0.12.14 to 0.12.15 (#516)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.14 to 0.12.15.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.14...v0.12.15)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:37:07 +00:00
dependabot[bot]
c53e198ea7
build(deps): bump redis from 0.29.1 to 0.29.2 (#517)
Bumps [redis](https://github.com/redis-rs/redis-rs) from 0.29.1 to 0.29.2.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.29.1...redis-0.29.2)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:36:54 +00:00
dependabot[bot]
4d9984ee76
build(deps): bump log from 0.4.26 to 0.4.27 (#518)
Bumps [log](https://github.com/rust-lang/log) from 0.4.26 to 0.4.27.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.26...0.4.27)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:36:08 +00:00
dependabot[bot]
9a08090709
build(deps): bump once_cell from 1.21.0 to 1.21.1 (#511)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.21.0 to 1.21.1.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.0...v1.21.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:49 +00:00
dependabot[bot]
7d3348bb29
build(deps): bump reqwest from 0.12.12 to 0.12.14 (#512)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.12 to 0.12.14.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/v0.12.14/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.12...v0.12.14)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:41 +00:00
dependabot[bot]
c0fa63af39
build(deps): bump uuid from 1.15.1 to 1.16.0 (#513)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.15.1 to 1.16.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.15.1...v1.16.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:39 +00:00
Rob Ede
0b5e2b3647
chore: check in lockfile 2025-03-11 03:12:27 +00:00
Rob Ede
b95595b9cd
chore(actix-cors): prepare release 0.7.1 2025-03-11 02:55:33 +00:00
dependabot[bot]
4b3f87e915
build(deps): update redis requirement from 0.28 to 0.29 (#510)
* build(deps): update redis requirement from 0.28 to 0.29

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.28.0...redis-0.29.1)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deadpool-redis to 0.20

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-03-11 02:38:37 +00:00
dependabot[bot]
144c7f92b9
build(deps): bump taiki-e/install-action from 2.47.32 to 2.49.9 (#505)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.47.32 to 2.49.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.47.32...v2.49.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 21:13:49 +00:00
dependabot[bot]
c71b9dd443
build(deps): bump codecov/codecov-action from 5.3.1 to 5.4.0 (#506)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.3.1...v5.4.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 21:13:17 +00:00
dependabot[bot]
282d56e96b
build(deps): bump actions-rust-lang/setup-rust-toolchain from 1.10.1 to 1.11.0 (#507)
* build(deps): bump actions-rust-lang/setup-rust-toolchain

Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.10.1 to 1.11.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.10.1...v1.11.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: lower msrv deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-03-01 21:01:02 +00:00
dependabot[bot]
d514ad3af5
build(deps): update rand requirement from 0.8 to 0.9 (#498)
* build(deps): update rand requirement from 0.8 to 0.9

Updates the requirements on [rand](https://github.com/rust-random/rand) to permit the latest version.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.8.0...0.9.0)

---
updated-dependencies:
- dependency-name: rand
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: fix rand upgrade items

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-02-23 18:57:16 +00:00
Rob Ede
109e6a4793
ci: fix test-msrv recipe 2025-02-23 18:53:00 +00:00
Rob Ede
bb0c7f21d9
ci: fix msrv by downgrading native-tls 2025-02-23 18:49:47 +00:00
Rob Ede
3f7a479a76
chore: update redis dependency to 0.28 2025-02-23 18:42:55 +00:00
Rob Ede
fc4b656c3b
chore: address clippy lints 2025-02-23 18:29:35 +00:00
dependabot[bot]
0f35de7da1
build(deps): update derive_more requirement from 1 to 2 (#502)
Updates the requirements on [derive_more](https://github.com/JelteF/derive_more) to permit the latest version.
- [Release notes](https://github.com/JelteF/derive_more/releases)
- [Changelog](https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JelteF/derive_more/compare/v1.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: derive_more
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 17:17:28 +00:00
dependabot[bot]
8294fcc645
build(deps): bump taiki-e/install-action from 2.47.2 to 2.47.32 (#499)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.47.2 to 2.47.32.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.47.2...v2.47.32)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 18:27:04 +00:00
dependabot[bot]
3de6b03711
build(deps): bump codecov/codecov-action from 5.1.2 to 5.3.1 (#500)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.2 to 5.3.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.1.2...v5.3.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 18:26:46 +00:00
Wren Turkal
64931189c7
Improve logout example (#496)
The current example for logout is not show a complete example.

I have added a couple lines to handle the case where a logged out user tries to logout again.
2025-01-16 11:56:12 +00:00
Keith Cirkel
265b213123
implement contains_key, update, update_or (#459)
* implement contains_key, update, update_or

* docs(session): update docs for new methods

* docs(session): clarify errors

* test(session): fix doctest

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-01-14 00:51:40 +00:00
Frank Elsinga
695369f02f
feat: added PartialEq to Cors (#486)
* added PartialEq to Cors

* added a changelog entry

* re-ran rustfmt

* removed a subtle bug in the new testcase

* removed a not so subtle bug in the new testcase

* ci: rm public-api-diff job

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-01-13 23:22:00 +00:00
laurens-dg
87d9e51112
docs(readme): fix typo and consistent dots in table (#494) 2025-01-10 13:31:31 +00:00
dependabot[bot]
8c11d37dda
build(deps): bump codecov/codecov-action from 5.0.7 to 5.1.2 (#492)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.0.7 to 5.1.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.0.7...v5.1.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 16:11:53 +00:00
dependabot[bot]
d97b36652a
build(deps): bump taiki-e/install-action from 2.45.13 to 2.47.2 (#493)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.45.13 to 2.47.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.45.13...v2.47.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 16:11:43 +00:00
Alex Wied
98847b9279
fix: ensure TCP connection is properly shut down when Session is dropped (#476)
* Ensure TCP connection is properly shut down when session is dropped

* Update CHANGELOG.md

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-29 16:57:56 +00:00
vgwidt
cd1b77134e
docs: fix redis tls feature names (#487)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-29 16:54:31 +00:00
Rob Ede
105932706d
chore: address clippy lints 2024-12-29 16:19:36 +00:00
Necdet Arda Etiman
18f94fa8b5
Added actix-jwt-cookies and actix-ws-broadcaster to README (#482)
* Added ctix-jwt-cookies to README

* added actix-ws-broadcaster to Community Crates

---------

Co-authored-by: Necdet Arda Etiman <necdetarda123@gmail.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-02 08:31:00 +00:00
dependabot[bot]
66b82f0f30
build(deps): bump actions-rust-lang/setup-rust-toolchain (#478)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.10.0...v1.10.1)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 08:28:15 +00:00
dependabot[bot]
d67abde5f3
build(deps): bump codecov/codecov-action from 4.6.0 to 5.0.7 (#483)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.6.0 to 5.0.7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.6.0...v5.0.7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 08:28:00 +00:00
Rob Ede
3eafe7f5ce
ci: fix public api diff 2024-12-02 10:21:30 +02:00
Rob Ede
3b5f7ae68c
docs: update readme 2024-12-02 10:21:30 +02:00
dependabot[bot]
036af488fd
build(deps): bump taiki-e/install-action from 2.44.15 to 2.45.13 (#484)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.44.15 to 2.45.13.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.44.15...v2.45.13)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 07:55:04 +00:00
Ross
77406cbb71
Added actix-web-validation to README (#474) 2024-10-13 16:32:52 +00:00
dependabot[bot]
2ede588693
build(deps): update redis requirement from 0.26 to 0.27 (#463)
* build(deps): update redis requirement from 0.26 to 0.27

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.26.0...redis-0.27.2)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelogs

* ci: fix doc

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-10-11 13:10:39 +00:00
jwiesler
21680e0ebe
Include cookie expiration and ttl in the identity example (#473) 2024-10-07 22:57:22 +00:00
dependabot[bot]
370f9d3033
build(deps): bump actions-rust-lang/setup-rust-toolchain (#469)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:47 +00:00
dependabot[bot]
8f4fb348b3
build(deps): bump taiki-e/install-action from 2.42.37 to 2.44.15 (#471)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.37 to 2.44.15.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.37...v2.44.15)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:34 +00:00
dependabot[bot]
ff4b173716
build(deps): bump codecov/codecov-action from 4.5.0 to 4.6.0 (#470)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:21 +00:00
William Desportes
49aacfce9f
Update README.md (#466) 2024-09-28 00:45:02 +00:00
Rob Ede
dd20ebb6cb
chore: allow missing docs on test mod 2024-09-12 15:10:28 -04:00
Rob Ede
a3211b73d3
chore(actix-identity): prepare release 0.8.0 2024-09-12 15:07:36 -04:00
Rob Ede
a89d3a58bc
refactor: fix nightly warning 2024-09-12 15:06:22 -04:00
Rob Ede
3c640ec120
chore: fix rand optionality 2024-09-12 14:56:33 -04:00
Rob Ede
26ccf8b200
chore: fix feature gate 2024-09-12 14:55:04 -04:00
Rob Ede
dd1421f1a0
chore(actix-session): prepare release 0.10.1 2024-09-12 14:50:48 -04:00
dependabot[bot]
4eb779be77
build(deps): bump taiki-e/install-action from 2.42.14 to 2.42.37 (#460)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.14 to 2.42.37.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.14...v2.42.37)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 03:02:50 +00:00
John Vandenberg
48646d1bd3
build(deps): update derive_more to v1.0 (#458)
* build(deps): update derive_more to v1.0

* chore: remove overspecified deps

* chore: use from the derive module

* chore: restore unrelated version reqs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-18 14:21:56 +00:00
Rob Ede
275675e1c2
docs(settings): fix doc test 2024-08-07 01:37:53 +01:00
Rob Ede
50d2fee4e2
chore(actix-settings): prepare release 0.8.0 2024-08-07 01:32:49 +01:00
Rob Ede
0c0d13be12
docs: update session dependants changelogs 2024-08-07 01:05:57 +01:00
Rob Ede
d10b71fe06
docs(session): doc adding features using cargo add 2024-08-07 01:03:40 +01:00
Rob Ede
f2339971cd
chore(actix-session): prepare release 0.10.0 2024-08-07 00:57:54 +01:00
dependabot[bot]
517e72f248
build(deps): update reqwest requirement from 0.11 to 0.12 (#454)
* build(deps): update reqwest requirement from 0.11 to 0.12

Updates the requirements on [reqwest](https://github.com/seanmonstar/reqwest) to permit the latest version.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.0...v0.12.5)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: use reqwest::StatusCode

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-06 14:19:02 +00:00
João Fernandes
504e89403b
feature(session): add deadpool-redis compatibility (#381)
* Add compatibility of `deadpool-redis` for the storage `redis_rs`.

* Keep up-to-date the `actix-redis` version.

* Format the project issued by command `cargo +nightly fmt`.

* Add `deadpool-redis` into the documentation and tests.

* Update CHANGES.md.

* Update the documentation of `Deadpool Redis` section on `redis_rs`.

* Replace `no_run` with `ignore` attribute on "Deadpool Redis" example to skip the doc tests failure.

* Rollback the renaming `redis::cmd` to `cmd` for better reading and avoid shadowing, fix the wrong return type on builder function comment.

* Format the project issued by command `cargo +nightly fmt`.

* Format.

* Fix feature naming from the last merge.

* Fix feature missing from the last merge.

* Format the project issued by command `cargo +nightly fmt`.

* Re-import `cookie-session` feature. (Maybe was removed accidentally from the last merge?)

* tmp

* chore: bump deadpool-redis to 0.16

* chore: fixup rest of redis code for pool

* fix: add missing cfg guard

* docs: fix pool docs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-06 13:47:49 +00:00
João Fernandes
31b1dc5aa8
feature(settings): add TLS (#380)
* Complete the missing TLS feature.

* Make the `cfg` attributes more clear.

* Format the project issued by command `cargo +nightly fmt`.

* Small changes on cargo file.

* Update CHANGES.md.

* Add documentation for `Tls::get_ssl_acceptor_builder()` and remove unused imports.

* Add the `cfg` macro with required feature on `TLS` tests.

* Update actix-settings/src/settings/tls.rs

Co-authored-by: Rob Ede <robjtede@icloud.com>

* Copy the workflow steps related to OpenSSL for windows from [actix-web workflow](a7375b6876/.github/workflows/ci.yml (L38-L45)).

* ci: install openssl 1.1.1

* Replaced `apply_settings` with `try_apply_settings` for a better error handling.

* Updated the example.

* Add `OpenSSL` error.

* Restrict `OpenSSL` error only for `tls` feature.

* Rename feature `tls` to `openssl`.

* Add doc feature `broken_intra_doc_links` to `get_ssl_acceptor_builder` function.

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-03 08:59:13 +00:00
dependabot[bot]
d7daf441d1
build(deps): bump taiki-e/install-action from 2.41.7 to 2.42.14 (#453)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-03 09:51:46 +01:00
dependabot[bot]
2de4b1886c
build(deps): update redis requirement from 0.25 to 0.26 (#451)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-30 12:05:02 +01:00
Rob Ede
caa5dbc5b3
chore: fix re-exports 2024-07-29 22:00:31 +01:00
Rob Ede
c259e715f8
chore: fix just doc 2024-07-29 21:59:53 +01:00
edgerunnergit
d8a86751f0
Make generate_session_key() public (#449)
* make generate_session_key() public and change impl to use DistString

* add changelong and use nightly fmt

* Add better support for receiving larger payloads (#430)

* Add better support for receiving larger payloads

This change enables the maximum frame size to be configured when receiving websocket frames. It also
adds a new stream time that aggregates continuation frames together into their proper collected
representation. It provides no mechanism yet for sending continuations.

* actix-ws: Add continuation & size config to changelog

* actix-ws: Add Debug, Eq to AggregatedMessage

* actix-ws: Add a configurable maximum size to aggregated continuations

* refactor: move aggregate types to own module

* test: fix chat example

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>

* docs(ws): update readme

* chore(actix-ws): prepare release 0.3.0

* chore(ws): remove unused dev dep

* chore: expose generate_session_key

* chore: fix import

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
Co-authored-by: asonix <asonix@asonix.dog>
2024-07-29 20:53:18 +00:00
Rob Ede
cac93d2bc7
chore(ws): remove unused dev dep 2024-07-20 07:32:11 +01:00
Rob Ede
95f4e0f692
chore(actix-ws): prepare release 0.3.0 2024-07-20 07:24:20 +01:00
Rob Ede
24f3985eab
docs(ws): update readme 2024-07-20 07:23:21 +01:00
asonix
b0d2947a4a
Add better support for receiving larger payloads (#430)
* Add better support for receiving larger payloads

This change enables the maximum frame size to be configured when receiving websocket frames. It also
adds a new stream time that aggregates continuation frames together into their proper collected
representation. It provides no mechanism yet for sending continuations.

* actix-ws: Add continuation & size config to changelog

* actix-ws: Add Debug, Eq to AggregatedMessage

* actix-ws: Add a configurable maximum size to aggregated continuations

* refactor: move aggregate types to own module

* test: fix chat example

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-20 06:09:30 +00:00
Rob Ede
0802eff40d
chore: fix fmt recipe 2024-07-20 07:11:31 +01:00
dependabot[bot]
2a6a36af23
build(deps): update prost requirement from 0.12 to 0.13 (#450)
* build(deps): update prost requirement from 0.12 to 0.13

Updates the requirements on [prost](https://github.com/tokio-rs/prost) to permit the latest version.
- [Release notes](https://github.com/tokio-rs/prost/releases)
- [Changelog](https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/prost/compare/v0.12.0...v0.13.1)

---
updated-dependencies:
- dependency-name: prost
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

* chore(actix-protobuf): prepare release 0.11.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-20 05:16:26 +00:00
dependabot[bot]
3ebdc6192c
build(deps): bump codecov/codecov-action from 4.4.1 to 4.5.0 (#446)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.4.1 to 4.5.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.4.1...v4.5.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:54 +00:00
dependabot[bot]
87cf947a45
build(deps): bump taiki-e/cache-cargo-install-action from 2.0.0 to 2.0.1 (#447)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:50 +00:00
dependabot[bot]
f063bec5ba
build(deps): bump taiki-e/install-action from 2.38.0 to 2.41.7 (#448)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.38.0 to 2.41.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.38.0...v2.41.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:48 +00:00
Rob Ede
45e9e00285
ci: fix coverage job 2024-06-20 03:10:20 +01:00
Rob Ede
6934db623b
ci: fix coverage job 2024-06-20 03:06:57 +01:00
Rob Ede
1a658a98e1
ci: fix coverage 2024-06-20 03:04:20 +01:00
Rob Ede
2a092a19a8
ci: coverage via just 2024-06-20 02:57:44 +01:00
Rob Ede
032aeb6fdb
chore: fix nightly warning 2024-06-20 02:28:58 +01:00
Rob Ede
52e58610e4
chore: share repo and website 2024-06-20 02:18:37 +01:00
Rob Ede
023158cfa8
chore: move lints to manifest 2024-06-20 02:14:35 +01:00
Rob Ede
14c605fae2
docs: indicative mood 2024-06-20 02:10:19 +01:00
Rob Ede
d94c023bf9
build: group recipes 2024-06-20 01:58:25 +01:00
Rob Ede
7e21fd753e
docs: clean up ws examples 2024-06-20 01:55:14 +01:00
Rob Ede
e7ee2a06ab
docs: split chat example 2024-06-20 01:37:37 +01:00
Rob Ede
8aa2c959c4
chore(actix-web-httpauth): prepare release 0.8.2 2024-06-11 04:02:32 +01:00
Rob Ede
2f1d1daee8
ci: fix doctest job 2024-06-11 03:56:49 +01:00
Rob Ede
d15572b501
ci: remove doc upload workflow 2024-06-11 03:56:08 +01:00
Rob Ede
b9e47d61c3
docs(httpauth): add HttpAuthentication::with_fn examples 2024-06-11 03:55:41 +01:00
Rob Ede
515a727ca3
docs(httpauth): rework example 2024-06-11 03:52:45 +01:00
Rob Ede
20234ec555
ci: fail on doc error 2024-06-11 03:52:15 +01:00
Rob Ede
e4bb5ed355
build: group recipes 2024-06-11 03:52:02 +01:00
dependabot[bot]
5368569d00
build(deps): bump actions-rust-lang/setup-rust-toolchain (#442)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-09 23:00:01 +00:00
dependabot[bot]
21b9408a23
build(deps): bump taiki-e/install-action from 2.34.1 to 2.38.0 (#443)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.34.1 to 2.38.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.34.1...v2.38.0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-09 22:59:50 +00:00
Rob Ede
5879740322
ci: monthly GHA updates 2024-06-09 23:40:37 +01:00
Rob Ede
4adc9f8884
docs(session): use standard docs 2024-06-09 23:38:33 +01:00
dependabot[bot]
abf75eeb06
build(deps): update redis requirement from 0.24 to 0.25 (#412)
* build(deps): update redis requirement from 0.24 to 0.25

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.24.0...redis-0.25.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* refactor(session): rename TLS features

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 22:29:13 +00:00
Rob Ede
433c926503
chore: address clippy lints 2024-06-09 23:08:20 +01:00
Rob Ede
8ebb12b75a
refactor: use tracing in session examples 2024-06-09 20:34:09 +01:00
zbigniewzolnierowicz
931c4eea4d
feat(session): add rustls (actix#342) (#402)
* feat(session): add rustls feature (actix#342)

* docs(session): fix weird grammar

* docs: update crate docs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 19:19:29 +00:00
asonix
8195484415
actix-ws: take the encoded buffer when yielding rather than split it (#435)
* actix-ws: take the encoded buffer when yielding rather than split it

* actix-ws: add memory reduction to changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 05:06:24 +00:00
Rob Ede
3ae4ef2706
docs: remove unnecessary code block annotations 2024-06-09 05:18:43 +01:00
dependabot[bot]
65c698cd7f
build(deps): bump taiki-e/install-action from 2.33.34 to 2.34.1 (#441)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.34 to 2.34.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.34...v2.34.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 17:38:33 +00:00
dependabot[bot]
f2ef72d056
build(deps): bump taiki-e/install-action from 2.33.26 to 2.33.34 (#440)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.26 to 2.33.34.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.26...v2.33.34)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 17:09:28 +00:00
dependabot[bot]
e4ee236341
build(deps): bump taiki-e/install-action from 2.33.22 to 2.33.26 (#436)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.22 to 2.33.26.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.22...v2.33.26)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:14:02 +00:00
dependabot[bot]
41ae57d414
build(deps): bump JamesIves/github-pages-deploy-action (#437)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:13:49 +00:00
dependabot[bot]
1b82024499
build(deps): bump codecov/codecov-action from 4.3.1 to 4.4.1 (#438)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.1 to 4.4.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.3.1...v4.4.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:13:30 +00:00
asonix
6b04450703
Enable sending Continuations from actix-ws (#431)
* Enable sending continuations from an actix-ws Session

* actix-ws: Allow sending continuations from Session

* Convert ignored doctests to no_run doctests

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-13 16:49:34 +00:00
dependabot[bot]
c0c7588a57
build(deps): bump taiki-e/install-action from 2.33.17 to 2.33.22 (#432)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 17:26:31 +01:00
dependabot[bot]
b918084a53
build(deps): bump codecov/codecov-action from 4.3.0 to 4.3.1 (#428)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.3.0...v4.3.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 19:39:46 +00:00
dependabot[bot]
b762b41360
build(deps): bump taiki-e/install-action from 2.33.9 to 2.33.17 (#429)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.9 to 2.33.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.9...v2.33.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 19:39:34 +00:00
dependabot[bot]
da53492c8c
build(deps): bump codecov/codecov-action from 4.2.0 to 4.3.0 (#421)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:24:33 +01:00
dependabot[bot]
2c81bc093b
build(deps): bump taiki-e/cache-cargo-install-action from 1.3.0 to 2.0.0 (#426)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 1.3.0 to 2.0.0.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v1.3.0...v2.0.0)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:33 +01:00
dependabot[bot]
a2ef65715b
build(deps): bump taiki-e/install-action from 2.32.9 to 2.33.9 (#425)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.9 to 2.33.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.32.9...v2.33.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:22 +01:00
dependabot[bot]
9beb348d45
build(deps): bump JamesIves/github-pages-deploy-action (#423)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:14 +01:00
dependabot[bot]
66544952b6
build(deps): bump codecov/codecov-action from 4.1.1 to 4.2.0 (#419)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.1 to 4.2.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.1.1...v4.2.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:13:54 +00:00
dependabot[bot]
9ddb95b74a
build(deps): bump taiki-e/install-action from 2.32.1 to 2.32.9 (#420)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.1 to 2.32.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.32.1...v2.32.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:13:40 +00:00
dependabot[bot]
f450e3fb85
build(deps): bump taiki-e/install-action from 2.29.7 to 2.32.1 (#417)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.29.7 to 2.32.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.29.7...v2.32.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 16:13:48 +00:00
dependabot[bot]
31951dcc9b
build(deps): bump codecov/codecov-action from 4.1.0 to 4.1.1 (#418)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 16:13:30 +00:00
dependabot[bot]
dfc6fe1986
build(deps): bump taiki-e/install-action from 2.29.0 to 2.29.7 (#414)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 17:52:13 +00:00
dependabot[bot]
122fba0580
build(deps): bump taiki-e/install-action from 2.28.11 to 2.29.0 (#413)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 15:04:23 +00:00
Rob Ede
f250348e57
chore: remove actix-redis crate (#408) 2024-03-12 23:30:06 +00:00
dependabot[bot]
e6f99e915d
build(deps): bump taiki-e/install-action from 2.28.1 to 2.28.11 (#411)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.28.1 to 2.28.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.28.1...v2.28.11)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 15:28:29 +00:00
dependabot[bot]
9d68074bf1
build(deps): bump taiki-e/install-action from 2.27.10 to 2.28.1 (#410)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.27.10 to 2.28.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.27.10...v2.28.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 16:06:35 +00:00
dependabot[bot]
bbb4ed047c
build(deps): bump codecov/codecov-action from 4.0.2 to 4.1.0 (#409)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.2 to 4.1.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.0.2...v4.1.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 16:06:19 +00:00
Rob Ede
39291c86b7
test: fix doc test 2024-03-02 21:46:36 +00:00
Rob Ede
db2193b8c5
chore: bump futures-* deps 2024-03-02 21:29:40 +00:00
dependabot[bot]
f0c33a970f
build(deps): bump taiki-e/install-action from 2.27.2 to 2.27.10 (#405)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.27.2 to 2.27.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.27.2...v2.27.10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-02 18:24:02 +00:00
Rob Ede
74c8545363
chore(actix-identity): prepare release 0.7.1 2024-03-02 18:32:58 +00:00
dependabot[bot]
9112cf9f23
build(deps): bump codecov/codecov-action from 4.0.1 to 4.0.2 (#406)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.0.1...v4.0.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-02 18:21:19 +00:00
Rob Ede
563d6e0b20
docs(session): add session_ttl aliases 2024-02-19 16:34:28 +00:00
dependabot[bot]
a71c7f6a90
build(deps): bump taiki-e/install-action from 2.26.8 to 2.27.2 (#404)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.26.8 to 2.27.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.26.8...v2.27.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 16:24:20 +00:00
Rob Ede
a5f5e31a82
chore: remove redundant imports 2024-02-19 16:18:17 +00:00
Dany Gagnon
5414e2655b
Add the ability to change the keys (#348)
* feat: add the ability to change the session key store in redis

* feat: change everywhere the constants are used

* refactor: add formatting with cargo fmt

---------

Co-authored-by: Dany Gagnon <danygagnon@Danys-MacBook-Pro.local>
2024-02-15 10:14:17 +00:00
John Vandenberg
daffc24245
Misc tidy up (#400) 2024-02-14 01:19:29 +00:00
dependabot[bot]
2e0cbb8bbb
build(deps): bump codecov/codecov-action from 3 to 4 (#397)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-02-05 16:25:37 +00:00
dependabot[bot]
7fe13e142e
build(deps): bump taiki-e/install-action from 2.26.7 to 2.26.13 (#396)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 16:10:26 +00:00
Marvin
8ddbf26cc1
doc(actix-cors): update MSRV (#395) 2024-01-31 16:18:13 +00:00
dependabot[bot]
b9769edca1
build(deps): bump taiki-e/install-action from 2.25.10 to 2.26.7 (#393)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.25.10 to 2.26.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.25.10...v2.26.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 17:08:12 +00:00
axelfaure
e3027549c5
Add a reference to Apistos to README.md (#390)
* Add a reference to Apistos to README.md

Apistos is a new crate for OpenAPI v3 documentation

* Actix Web

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-01-26 16:18:45 +00:00
dependabot[bot]
1934457e48
build(deps): bump taiki-e/install-action from 2.25.2 to 2.25.10 (#389)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.25.2 to 2.25.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.25.2...v2.25.10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 16:06:21 +00:00
dependabot[bot]
254d4084a9
build(deps): update env_logger requirement from 0.10 to 0.11 (#388)
Updates the requirements on [env_logger](https://github.com/rust-cli/env_logger) to permit the latest version.
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 15:56:30 +00:00
dependabot[bot]
a9e615bac4
build(deps): bump taiki-e/install-action from 2.24.1 to 2.25.2 (#385)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.24.1 to 2.25.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.24.1...v2.25.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 16:02:32 +00:00
dependabot[bot]
1e70159e08
build(deps): bump actions-rust-lang/setup-rust-toolchain (#384)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.6.0 to 1.8.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.6.0...v1.8.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 16:02:19 +00:00
Rob Ede
89bf63e1ef
chore(actix-identity): prepare release 0.7.0 2024-01-11 04:44:02 +00:00
Rob Ede
8b4e8ea34e
chore(actix-session): prepare release 0.9.0 2024-01-11 04:27:56 +00:00
dependabot[bot]
5ceb3c72cd
build(deps): bump taiki-e/install-action from 2.23.7 to 2.24.1 (#382)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.23.7 to 2.24.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.23.7...v2.24.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 15:36:45 +00:00
Rob Ede
c62b271d9a
chore(actix-cors): prepare release 0.7.0 2024-01-06 21:13:26 +00:00
Rob Ede
320cbebc7e
chore: fmt markdowns 2024-01-06 21:08:09 +00:00
Rob Ede
0c859a96c8
docs(cors): use cargo-rdme 2024-01-06 21:02:36 +00:00
Rob Ede
d55fc6d7f5
fix!(cors): default block_on_origin_mismatch to false (#379) 2024-01-06 20:40:44 +00:00
Rob Ede
e2bf504055
feat(session): use real async traits (#365) 2024-01-04 04:10:46 +00:00
Rob Ede
77b8dcdf59
chore: clippy 2024-01-04 04:05:56 +00:00
Rob Ede
b694c9317a
mark Cors builder as must_use 2024-01-04 03:42:10 +00:00
dependabot[bot]
57eaad2ffe
build(deps): bump taiki-e/install-action from 2.23.1 to 2.23.7 (#377)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.23.1 to 2.23.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.23.1...v2.23.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 15:42:56 +00:00
Rob Ede
0cb0e28208
ci: combine install steps 2023-12-26 04:05:35 +00:00
Rob Ede
8049a75d9f
ci: use cargo-ci-clean-cache 2023-12-26 03:58:01 +00:00
dependabot[bot]
0dd810e213
build(deps): bump taiki-e/install-action from 2.22.6 to 2.23.1 (#376)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.22.6 to 2.23.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.22.6...v2.23.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 15:45:53 +00:00
dependabot[bot]
5bf831c27b
build(deps): bump taiki-e/install-action from 2.22.0 to 2.22.6 (#375)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.22.0 to 2.22.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.22.0...v2.22.6)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 16:25:03 +00:00
dependabot[bot]
a7e3503ad1
build(deps): bump taiki-e/install-action from 2.21.26 to 2.22.0 (#374)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.26 to 2.22.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.26...v2.22.0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:53:08 +00:00
dependabot[bot]
819f45106f
build(deps): update redis requirement from 0.23 to 0.24 (#373)
Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.23.0...redis-0.24.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:52:47 +00:00
Rob Ede
4f76943423
chore(actix-cors): prepare release 0.6.5 2023-12-06 17:09:20 -05:00
yhx-12243
2f30fd71a9
fix(cors): The item in "Vary" header should be "Access-Control-Request-Private-Network". (#369)
* fix(cors): vary should be "Access-Control-Request-Private-Network"

* docs(cors): update the changelog
2023-12-06 13:52:22 +00:00
dependabot[bot]
5198c68c06
Bump JamesIves/github-pages-deploy-action from 4.4.3 to 4.5.0 (#372)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.4.3 to 4.5.0.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.4.3...v4.5.0)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 16:06:33 +00:00
dependabot[bot]
2d4cf5f422
Bump taiki-e/install-action from 2.21.20 to 2.21.26 (#370)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.20 to 2.21.26.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.20...v2.21.26)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 16:05:21 +00:00
dependabot[bot]
53dce5c34f
Bump actions-rust-lang/setup-rust-toolchain from 1.5.0 to 1.6.0 (#371)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 16:03:40 +00:00
dependabot[bot]
8de686a711
Bump taiki-e/install-action from 2.21.17 to 2.21.20 (#368)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.17 to 2.21.20.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.17...v2.21.20)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 15:39:43 +00:00
dependabot[bot]
50fd71d496
Bump taiki-e/install-action from 2.21.11 to 2.21.17 (#367)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.11 to 2.21.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.11...v2.21.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 15:59:06 +00:00
Phillip Wenig
3c5478966f
add actix-telepathy to community crate list (#364)
Co-authored-by: Phillip W <info@pwenig.de>
2023-11-15 20:51:47 +00:00
dependabot[bot]
1e18d62852
Bump taiki-e/install-action from 2.21.7 to 2.21.11 (#363)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.7 to 2.21.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.7...v2.21.11)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 16:05:05 +00:00
Rob Ede
7aeeb9a445
ci: use giraffate/clippy-action for clippy 2023-11-10 14:06:23 +00:00
Rob Ede
5b2085f414
ci: fix clippy lint 2023-11-10 13:57:49 +00:00
Rob Ede
6afca96ddf
ci: disallow dbg macro 2023-11-10 13:55:20 +00:00
dependabot[bot]
a48c2926f9
Bump taiki-e/install-action from 2.21.3 to 2.21.7 (#362)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.21.3 to 2.21.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.21.3...v2.21.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 18:31:30 +00:00
Rob Ede
6d0ab96dfd
adopt actix-ws crate (#361) 2023-11-03 22:49:18 +00:00
Rob Ede
a593a8dc90
fix readme version 2023-11-03 20:39:32 +00:00
Rob Ede
4d79d263ef
update readme versions 2023-11-03 20:37:18 +00:00
Rob Ede
31540f8e4b
chore(actix-settings): prepare release 0.7.1 2023-11-03 20:30:00 +00:00
Rob Ede
11046d7663
chore(actix-settings): prepare release 0.7.0 2023-11-03 20:24:04 +00:00
Rob Ede
73b2aac6d6
make Settings::from_default_template infallible 2023-11-03 20:23:55 +00:00
Heki
76d9313171
Allow ActixSettings to be applied to HttpServer in actix-settings (#321)
* allow other settings objects to be applied

* update changelog

* update changelog

---------

Co-authored-by: LinuxHeki <linuxheki@gmail.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-11-03 20:16:31 +00:00
Rob Ede
373a89a978
fix warnings on doc upload workflow 2023-11-03 20:02:50 +00:00
Rob Ede
61f16c609a
fix doc warnings 2023-11-03 20:00:56 +00:00
Rob Ede
ecd2016c09
use standard attributes for all crates 2023-11-03 19:46:12 +00:00
Rob Ede
471f07e27f
improve actix-cors docs 2023-11-03 19:44:12 +00:00
dependabot[bot]
077c6edced
Bump taiki-e/install-action from 2.20.15 to 2.21.3 (#360)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.20.15 to 2.21.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.20.15...v2.21.3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 15:54:04 +00:00
Kunal Singh
fad631c448
fix: typo in actix-session cargo.toml file (#356) 2023-10-29 22:37:57 +00:00
dependabot[bot]
20f72cab3e
Bump taiki-e/install-action from 2.20.3 to 2.20.15 (#355)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.20.3 to 2.20.15.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.20.3...v2.20.15)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 16:07:54 +00:00
dependabot[bot]
4bad825456
Bump taiki-e/cache-cargo-install-action from 1.2.2 to 1.3.0 (#353)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v1.2.2...v1.3.0)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 19:00:57 +00:00
dependabot[bot]
cb3eba93cc
Bump taiki-e/install-action from 2.20.2 to 2.20.3 (#352)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.20.2 to 2.20.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.20.2...v2.20.3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 15:43:04 +00:00
Rob Ede
9d993c6c73
ci: reduce dependabot rate 2023-10-13 17:42:47 +02:00
dependabot[bot]
4761826616
Bump taiki-e/install-action from 2.20.1 to 2.20.2 (#351)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.20.1 to 2.20.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.20.1...v2.20.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 08:55:19 +00:00
dependabot[bot]
ec340670a8
Bump taiki-e/install-action from 2.19.4 to 2.20.1 (#350)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.19.4 to 2.20.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.19.4...v2.20.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 16:10:46 +00:00
dependabot[bot]
3a7834c3ba
Bump taiki-e/install-action from 2.19.3 to 2.19.4 (#349)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 17:01:41 +01:00
dependabot[bot]
7db43782ce
Bump taiki-e/install-action from 2.19.1 to 2.19.3 (#347)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.19.1 to 2.19.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.19.1...v2.19.3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 15:56:59 +00:00
dependabot[bot]
1ee1afb2a6
Bump taiki-e/install-action from 2.18.17 to 2.19.1 (#345)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.17 to 2.19.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.17...v2.19.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 15:49:25 +00:00
dependabot[bot]
9e4754bbfa
Bump taiki-e/install-action from 2.18.16 to 2.18.17 (#344)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.16 to 2.18.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.16...v2.18.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 15:27:25 +00:00
dependabot[bot]
cd3e5f9772
Bump taiki-e/install-action from 2.18.15 to 2.18.16 (#341)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.15 to 2.18.16.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.15...v2.18.16)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 15:24:50 +00:00
dependabot[bot]
45ee50f9cb
Bump taiki-e/install-action from 2.18.14 to 2.18.15 (#340)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.14 to 2.18.15.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.14...v2.18.15)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-22 16:46:27 +00:00
dependabot[bot]
1d6ef8938f
Bump taiki-e/install-action from 2.18.13 to 2.18.14 (#339)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.13 to 2.18.14.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.13...v2.18.14)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 16:12:18 +00:00
dependabot[bot]
316c0d238d
Bump taiki-e/install-action from 2.18.11 to 2.18.13 (#338)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 16:20:41 +01:00
dependabot[bot]
5baa3c3d95
Bump taiki-e/cache-cargo-install-action from 1.2.1 to 1.2.2 (#337)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 16:20:24 +01:00
Rob Ede
2dea1f2748
docs: update dep shields 2023-09-16 07:52:18 +01:00
Rob Ede
09ff35bd2d
chore: fix tests for settings 2023-09-16 03:44:47 +01:00
Rob Ede
6caf37cedd
chore(actix-limitation): prepare release 0.5.1 2023-09-16 03:36:29 +01:00
Rob Ede
bafd8179ff
chore: fix actix web feature specs 2023-09-16 03:35:56 +01:00
Rob Ede
3fad53211a
chore(actix-limitation): prepare release 0.5.0 2023-09-16 03:29:58 +01:00
Rob Ede
9a7113028e
chore(actix-identity): prepare release 0.6.0 2023-09-16 03:29:31 +01:00
Rob Ede
9e31f5b306
chore(actix-session): prepare release 0.8.0 2023-09-16 03:28:31 +01:00
Rob Ede
94f99e4843
chore(actix-web-httpauth): prepare release 0.8.1 2023-09-16 03:14:40 +01:00
Rob Ede
600dda5ef3
chore: correct futures-util dep specs 2023-09-16 03:14:04 +01:00
Rob Ede
2a074ddf18
chore(actix-redis): prepare release 0.13.0 2023-09-16 03:07:34 +01:00
Rob Ede
9fc34a9c48
chore(actix-protobuf): prepare release 0.10.0 2023-09-16 03:07:01 +01:00
dependabot[bot]
f942d8a191
Update redis-async requirement from 0.14 to 0.16 (#336)
* Update redis-async requirement from 0.14 to 0.16

Updates the requirements on [redis-async](https://github.com/benashford/redis-async-rs) to permit the latest version.
- [Commits](https://github.com/benashford/redis-async-rs/commits)

---
updated-dependencies:
- dependency-name: redis-async
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-16 00:33:09 +00:00
dependabot[bot]
b737452294
Update redis requirement from 0.22 to 0.23 (#334)
* Update redis requirement from 0.22 to 0.23

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.22.0...redis-0.23.3)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-16 00:30:20 +00:00
Max Karou
55ace79d64
Implement From<Basic> for BasicAuth (#327)
* implement `From<Basic>` for `BasicAuth`

* update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-16 00:27:50 +00:00
dependabot[bot]
c029287801
Update prost requirement from 0.11 to 0.12 (#333)
* Update prost requirement from 0.11 to 0.12

Updates the requirements on [prost](https://github.com/tokio-rs/prost) to permit the latest version.
- [Release notes](https://github.com/tokio-rs/prost/releases)
- [Commits](https://github.com/tokio-rs/prost/compare/prost-build-0.11.1...v0.12.1)

---
updated-dependencies:
- dependency-name: prost
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-16 00:25:08 +00:00
Rob Ede
0d27e3a65a
feat(settings): impl Error for Error 2023-09-16 01:19:16 +01:00
dependabot[bot]
257871ca7a
Update toml requirement from 0.5 to 0.8 (#335)
* Update toml requirement from 0.5 to 0.8

Updates the requirements on [toml](https://github.com/toml-rs/toml) to permit the latest version.
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.5.0...toml-v0.8.0)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs(settings): update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-15 23:59:19 +00:00
dependabot[bot]
d921417726
Bump JamesIves/github-pages-deploy-action from 3.7.1 to 4.4.3 (#330)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 3.7.1 to 4.4.3.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/3.7.1...v4.4.3)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 23:54:21 +00:00
dependabot[bot]
70b46280ed
Bump actions/checkout from 3 to 4 (#332)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 23:54:02 +00:00
dependabot[bot]
55d70231cc
Bump taiki-e/install-action from 2.18.9 to 2.18.11 (#329)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.18.9 to 2.18.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.18.9...v2.18.11)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 23:43:34 +00:00
Rob Ede
aaedb9c625
MSRV 1.68 (#328) 2023-09-16 00:30:38 +01:00
Rob Ede
75386f4a1d
ensure docs.rs builds all crates with all features 2023-04-11 12:22:28 +01:00
Rob Ede
8a31f3020e
revert local-network change
see https://github.com/actix/actix-extras/pull/320#issuecomment-1501189129
2023-04-09 19:56:43 +01:00
Rob Ede
8c93f5314b
update readme crate versions 2023-04-09 19:47:57 +01:00
Rob Ede
f37c93a2a8
migrate to doc_auto_cfg 2023-04-09 19:41:57 +01:00
Rob Ede
111d95eaea
rename private-network-access feature (#320)
* update CI with concurrency options

* cors: rename private-network => local-network

* modernize CI

* clippy

* run api diff job on all features
2023-04-09 19:35:30 +01:00
Rob Ede
8729f60f79
fix CI MSRV 2023-03-23 12:15:25 +00:00
Rob Ede
77ee27b4ae
inline workspace package properties
msrv needs to be 1.64
2023-03-23 11:40:54 +00:00
Rob Ede
b948ac9f7a
fix MSRV in CI 2023-03-23 10:53:54 +00:00
Rob Ede
ad1f15eb18
centralize msrv and edition specs 2023-03-22 21:17:30 +00:00
Rob Ede
8a9c604c03
update msrv to 1.60
promted by prost 0.11.8
2023-03-22 21:13:56 +00:00
Rob Ede
218f18e69d
fix default features attributes 2023-03-22 20:51:14 +00:00
citreae535
2bc16eee18
Update base64 dependency to 0.21 (#316) 2023-01-30 16:53:30 +00:00
Jacobtread
713b157fd4
Corrected actix-form-data community crate details (#314) 2023-01-13 10:43:52 +00:00
Rob Ede
bf49b39740
use secure tokio version range
see RUSTSEC-2023-0001

part of actix/actix-web#2962
2023-01-10 09:03:27 +00:00
Rob Ede
441d604c00
derive identity error impls 2023-01-07 02:22:13 +00:00
Joseph McCormick
1ed893a08c
Feature: Add IdentityError to actix-identity crate. (#296)
* Add IdentityError to actix-identity crate.

In order to let crates in the actix web ecosystem interact correctly
with `actix_web::Error`, this commit introduces its own error type,
replacing the previous usage of `anyhow::Error`.

* Mend some clippy warnings on IdentityError.

* Split identity error into more granular versions.

- `MissingIdentityError` occurs whenever we attempt to gather
  information about an identity from a session, and fail.
- `LoginError` occurs whenever we attempt to login via an identity, and
  fail.

* Feedback for identity error implementation.

- `IdentityError` -> `GetIdentityError`
- Move error messages into Display impl where appropriate
- Split `id` and `get_identity` errors into two types
- Implement `source` on custom errors

* Expand identity error types with struct markers.

In order to get a little more future compatibility and reduce
abstraction leaking, this commit introduces some contextual structs to
our identity errors package.

* Improve doc message for SessionExpiryError.

Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>

* Improve identity error docs and messaging.

Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>

* Expand LostIdentityError with placeholder.

Adds a placeholder unit struct to the LostIdentityError variant of
GetIdentityError, which should let us expand on that variant with extra
context later if we like.

* Add From coercion for LostIdentityError.

Improve the ergonomics of using the LostIdentityError unit struct.

* Update Cargo.toml

* Update CHANGES.md

* expose identity error module

* fix error impl

Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-01-07 02:05:12 +00:00
Rob Ede
708aa945dc
workaround msrv issues fix 2023-01-07 01:57:03 +00:00
Rob Ede
9be4f1ff73
workaround dev dep msrv issues 2023-01-07 01:38:07 +00:00
Rob Ede
f8a1165d10
fix manifest 2023-01-07 01:17:45 +00:00
Rob Ede
d9175a0399
update base64 dep to 0.20 2023-01-07 01:16:14 +00:00
Rob Ede
fe4d3d366d
Update redis dependency to 0.22 2023-01-07 01:15:26 +00:00
Rob Ede
1036f54fd0
update redis-async to 0.14 2023-01-07 01:09:34 +00:00
Rob Ede
e9428ba261
update env_logger dev dep 2023-01-07 01:08:01 +00:00
Rob Ede
779860b664
clippy 2023-01-07 01:04:16 +00:00
Rob Ede
6848312467
prettier markdown changelogs 2023-01-07 01:02:02 +00:00
Peihao Yang
8c509151f1
add sentinel middleware to community crates (#312) 2023-01-05 14:36:31 +00:00
Yuki Okushi
1774b8a36e
Fix GHA deprecation warnings (#301) 2022-12-01 10:45:31 +00:00
aalhitennf
9508be94d5
Update README.md (#304) 2022-11-12 19:52:29 +00:00
aalhitennf
8e76c6c628
add bincode extractor lib to community crates (#303) 2022-11-12 13:26:16 +00:00
Even O. Rogstadkjærnet
8fd166435f
Add secure field to removal cookie (#300)
Closes https://github.com/actix/actix-extras/issues/299
2022-11-08 09:29:23 +00:00
Rob Ede
1ac325ab79
fix cors changelog 2022-10-30 16:28:21 +00:00
Rob Ede
b95ce3a210
prepare actix-cors release 0.6.4 2022-10-30 16:25:52 +00:00
Rob Ede
ac444ca798
add support for private network access cors header (#297)
closes #294
2022-10-28 23:44:21 +01:00
Yuki Okushi
fb8a814acb
session: Fix a typo in a link to actix-redis (#293) 2022-10-15 12:36:59 +01:00
Rob Ede
da0a806e8d
clippy 2022-09-25 21:08:36 +01:00
Duy Do
d28ab6eaa1
CORS origin does not end with / (#291) 2022-09-22 11:46:24 +00:00
Rob Ede
a2c5cbd637
fix cors changelog 2022-09-22 00:25:48 +01:00
Rob Ede
e6ef190510
prepare actix-cors release 0.6.3 2022-09-22 00:24:38 +01:00
CapableWeb
3b5682c860
Add block_on_origin_mismatch option to middleware (#287)
Co-authored-by: CapableWeb <capableweb@domain.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-09-21 23:22:20 +00:00
Moritz Hedtke
82a100d96c
Add note for accessing session state in stream. (#285) 2022-09-21 12:51:45 +00:00
Marko Malenic
d98ebf2bdf
Update Cors function documentation to match behaviour (#289)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-09-21 09:15:54 +00:00
Rob Ede
1561bda822
apply imports_granularity fmt rule 2022-09-11 21:55:40 +01:00
Rob Ede
339b81e843
prepare actix-session release 0.7.2 2022-09-11 21:13:20 +01:00
Rob Ede
eb3660a772
set same-site attribute when clearing session cookie (#284)
fixes #282
2022-09-11 21:11:33 +01:00
Rob Ede
9a3b410409
prepare actix-limitation release 0.4.0 2022-09-11 00:06:57 +01:00
Raphael C
32313c0af6
Limitation: custom key from closure (#281)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-09-10 23:02:54 +00:00
Raphael C
a623c50e9c
Limitation: display and handle client error (#280)
* feat(limitation): display and handle client error

* feat(limitation): handle other count errors

* feat: add middleware errors catch changes to changelog

Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-08-28 20:49:14 +01:00
Rob Ede
7d932cd540
bump msrv to 1.59 2022-08-28 20:30:32 +01:00
Rob Ede
ffe122b76e
prepare actix-protobuf release 0.9.0 2022-08-24 18:07:47 +01:00
Rob Ede
1e682e7a59
update to prost 0.11 (#279)
* updated to prost 0.11 and added application/x-protobuf

* updated derive-more, prost, futures-util versions

* updated Changelog and a small fix in Cargo.toml

* cargo fmt

* bumped version to 0.8.1

* removed version bump

* add back intentional patch versions

Co-authored-by: Ahmed Masud <ahmed.masud@saf.ai>
2022-08-24 18:07:13 +01:00
Rob Ede
e61dbae860
rename AtError => Error (#275)
* refactor(settings)!: rename AtError => Error

and remove AtResult from public API

* update changelog

* recover from file metadata errors
2022-08-10 09:13:34 +01:00
Rob Ede
a325f5dd02
add some doc examples to -session 2022-08-09 01:03:28 +01:00
Rob Ede
bad6159516
prepare actix-cors release 0.6.2 2022-08-07 20:57:09 +01:00
Rob Ede
7c3c9357e0
fix expose all headers (#273)
* fix expose all headers

* update changelog
2022-08-07 20:56:33 +01:00
Rob Ede
bcb8dbe1fc
add config file to example 2022-07-31 20:26:40 +01:00
Rob Ede
983746f106
prepare actix-settings release 0.6.0 2022-07-31 20:13:27 +01:00
Rob Ede
b054733854
document settings crate (#271) 2022-07-31 20:12:19 +01:00
Rob Ede
ab3f591307
fmt 2022-07-31 15:34:52 +01:00
Rob Ede
c08cd8a23a
add standard crate lints 2022-07-31 15:33:22 +01:00
Rob Ede
da32c1bb49
rename settings modules 2022-07-31 15:30:50 +01:00
Rob Ede
90766e5d68
use panics in tests for better diagnostics 2022-07-31 15:18:23 +01:00
Rob Ede
f678842e46
modululize -settings 2022-07-31 15:10:22 +01:00
Rob Ede
e13b62fc6b
adopt actix-settings crate (#270)
* adopt actix-settings crate

* add licenses and readme addition

* revamp readme

* delete temp prettier file
2022-07-31 14:44:45 +01:00
Rob Ede
6e79465362
make RateLimiter non-exhaustive 2022-07-31 03:03:43 +01:00
Rob Ede
cd9dc163e5
prepare actix-session release 0.7.1 2022-07-24 15:29:50 +01:00
Mohamed Emad
810a88a156
fix: bad interaction between session state changes and renewal (#265) 2022-07-24 14:27:25 +00:00
Rob Ede
cfd16c5478
clippy 2022-07-21 22:20:48 +01:00
Rob Ede
07c5176bd0
align descriptions 2022-07-21 03:07:06 +01:00
Rob Ede
446c92c3d0
update proto deb badge 2022-07-21 02:54:53 +01:00
Rob Ede
65a6252fec
update crate dep badges 2022-07-21 02:54:11 +01:00
Rob Ede
73732b0a62
prepare actix-web-httpauth release 0.8.0 2022-07-21 02:51:33 +01:00
Rob Ede
ff06958b32
improve httpauth ergonomics (#264)
* improve httpauth ergonomics

* update changelog

* code and docs cleanup

* docs

* docs clean

* remove AuthExtractor trait

* update changelog
2022-07-21 02:50:22 +01:00
Rob Ede
4d2f4d58b4
clippy 2022-07-21 00:07:49 +01:00
Mike Cronce
140453c649
Return &str from BasicAuth::user_id() and BasicAuth::password() (#249)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-07-19 01:33:32 +00:00
Rob Ede
fbae63d07f
prepare actix-web-httpauth release 0.7.0 2022-07-19 01:52:04 +01:00
Roland
417c06b00e
fix: broken stream when authentication failed (#260)
* fix: broken http stream when authentication failed

Signed-off-by: Roland Ma <rolandma@outlook.com>

* remove unchanged

Signed-off-by: Roland Ma <rolandma@outlook.com>

* Update CHANGES.md

* Update CHANGES.md

* Update CHANGES.md

Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-07-19 01:40:01 +01:00
Rob Ede
553c2bfb92
prepare actix-identity release 0.5.2 2022-07-19 01:33:53 +01:00
Luca Palmieri
1089faaf93
[actix-identity] Fix visit deadline (#263) 2022-07-19 01:31:31 +01:00
Rob Ede
1cc37c371e
prepare actix-identity release 0.5.1 2022-07-11 18:07:50 +01:00
Rob Ede
d853c115b6
trim unnecessary identity deps (#259)
* trim unnecessary identity deps

* update changelog
2022-07-11 18:07:26 +01:00
Rob Ede
603215095a
prepare actix-identity release 0.5.0 2022-07-11 13:39:14 +01:00
Luca Palmieri
d3fb564380
Add changelog for actix-identity (#258) 2022-07-11 12:46:49 +01:00
Rob Ede
ee71d4cfa7
update readme 2022-07-11 02:15:47 +01:00
141 changed files with 56257 additions and 2176 deletions

View File

@ -3,35 +3,41 @@ name: bug report
about: create a bug report
---
Your issue may already be reported!
Please search on the [actix-extras issue tracker](https://github.com/actix/actix-extras/issues) before creating one.
Your issue may already be reported! Please search on the [actix-extras issue tracker](https://github.com/actix/actix-extras/issues) before creating one.
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Rust Version (I.e, output of `rustc -V`):
* Actix-* crate(s) Version:
- Rust version (output of `rustc -V`):
- `actix-*` crate versions:

View File

@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Gitter channel (actix)
url: https://gitter.im/actix/actix
about: Please ask and answer questions about the actix project here.

View File

@ -2,12 +2,14 @@
<!-- Please fill out the following to make our reviews easy. -->
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
## PR Checklist
<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->
@ -16,11 +18,10 @@ INSERT_PR_TYPE
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the nightly rustfmt (`cargo +nightly fmt`).
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly

View File

@ -1,8 +1,13 @@
name: CI (post-merge)
on:
push:
branches: [master]
push: { branches: [master] }
permissions: { contents: read }
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_and_test_linux_nightly:
@ -11,10 +16,8 @@ jobs:
matrix:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
version:
- nightly
name: ${{ matrix.target.name }} / ${{ matrix.version }}
name: ${{ matrix.target.name }} / nightly
runs-on: ${{ matrix.target.os }}
services:
@ -25,104 +28,80 @@ jobs:
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
toolchain: nightly
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
- name: Install cargo-hack, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.49.42
with:
command: generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
tool: cargo-hack,cargo-ci-cache-clean
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-min }
run: cargo ci-min
- name: check minimal + examples
uses: actions-rs/cargo@v1
with: { command: ci-check-min-examples }
run: cargo ci-check-min-examples
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check }
run: cargo ci-check
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with: { command: ci-test }
run: cargo ci-test
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache
- name: CI cache clean
run: cargo-ci-cache-clean
build_and_test_other_nightly:
strategy:
fail-fast: false
# prettier-ignore
matrix:
target:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version:
- nightly
name: ${{ matrix.target.name }} / ${{ matrix.version }}
name: ${{ matrix.target.name }} / nightly
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
toolchain: nightly
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.49.42
with:
command: generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
tool: cargo-hack,cargo-ci-cache-clean
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-min }
run: cargo ci-min
- name: check minimal + examples
uses: actions-rs/cargo@v1
with: { command: ci-check-min-examples }
run: cargo ci-check-min-examples
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check }
run: cargo ci-check
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: ci-test
args: >-
--exclude=actix-redis
--exclude=actix-session
--exclude=actix-limitation
-- --nocapture
run: cargo ci-test --exclude=actix-session --exclude=actix-limitation -- --nocapture
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache
- name: CI cache clean
run: cargo-ci-cache-clean

View File

@ -3,9 +3,17 @@ name: CI
on:
pull_request:
types: [opened, synchronize, reopened]
merge_group:
types: [checks_requested]
push:
branches: [master]
permissions: { contents: read }
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_and_test_linux:
strategy:
@ -14,10 +22,10 @@ jobs:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
version:
- 1.57 # MSRV
- stable
- { name: msrv, version: 1.75.0 }
- { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version }}
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
runs-on: ${{ matrix.target.os }}
services:
@ -33,130 +41,113 @@ jobs:
--entrypoint redis-server
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
- name: Install cargo-hack and cargo-ci-cache-clean, just
uses: taiki-e/install-action@v2.49.42
with:
command: generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
tool: cargo-hack,cargo-ci-cache-clean,just
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: just downgrade-for-msrv
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-min }
run: cargo ci-min
- name: check minimal + examples
uses: actions-rs/cargo@v1
with: { command: ci-check-min-examples }
run: cargo ci-check-min-examples
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check }
run: cargo ci-check
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with: { command: ci-test }
run: cargo ci-test
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache
- name: CI cache clean
run: cargo-ci-cache-clean
build_and_test_other:
strategy:
fail-fast: false
matrix:
# prettier-ignore
target:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version:
- 1.57 # MSRV
- stable
- { name: msrv, version: 1.75.0 }
- { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version }}
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
- name: Install cargo-hack, cargo-ci-cache-clean, just
uses: taiki-e/install-action@v2.49.42
with:
command: generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
tool: cargo-hack,cargo-ci-cache-clean,just
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: just downgrade-for-msrv
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-min }
run: cargo ci-min
- name: check minimal + examples
uses: actions-rs/cargo@v1
with: { command: ci-check-min-examples }
run: cargo ci-check-min-examples
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check }
run: cargo ci-check
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: ci-test
args: >-
--exclude=actix-redis
--exclude=actix-session
--exclude=actix-limitation
run: cargo ci-test --exclude=actix-session --exclude=actix-limitation
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
cargo-cache
- name: CI cache clean
run: cargo-ci-cache-clean
doc_tests:
name: doc tests
name: Documentation Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rs/toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal
override: true
toolchain: nightly
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.3.0
- name: doc tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
- name: Install just
uses: taiki-e/install-action@v2.49.42
with:
command: ci-doctest
args: -- --nocapture
tool: just
- name: Test docs
run: just test-docs
- name: Build docs
run: just doc

View File

@ -1,11 +1,15 @@
# disabled because `cargo tarpaulin` currently segfaults
name: Coverage
on:
push:
branches: [master]
permissions: { contents: read }
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
coverage:
runs-on: ubuntu-latest
@ -18,25 +22,26 @@ jobs:
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install stable
uses: actions-rs/toolchain@v1
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
toolchain: nightly
components: llvm-tools-preview
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@v2.49.42
with:
tool: just,cargo-llvm-cov,cargo-nextest
- name: Generate code coverage
run: just test-coverage-codecov
- name: Generate coverage file
run: |
cargo install cargo-tarpaulin --vers "^0.13"
cargo tarpaulin --workspace --out Xml --verbose
- name: Upload to Codecov
uses: codecov/codecov-action@v1
with: { file: cobertura.xml }
uses: codecov/codecov-action@v5.4.0
with:
files: codecov.json
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@ -1,39 +1,48 @@
name: Lint
on:
pull_request:
types: [opened, synchronize, reopened]
on: [pull_request]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
components: rustfmt
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run: cargo fmt --all -- --check
clippy:
permissions:
contents: read
checks: write # to add clippy checks to PR diffs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: stable
components: clippy
override: true
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
uses: giraffate/clippy-action@v1.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --all-features
reporter: github-pr-check
github_token: ${{ secrets.GITHUB_TOKEN }}
clippy_flags: >-
--workspace --all-features --tests --examples --bins --
-A unknown_lints -D clippy::todo -D clippy::dbg_macro

View File

@ -1,35 +0,0 @@
name: Upload Documentation
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Build Docs
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --all-features --no-deps
- name: Tweak HTML
run: echo '<meta http-equiv="refresh" content="0;url=actix_cors/index.html">' > target/doc/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: target/doc

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
guide/build/
/gh-pages
@ -12,3 +11,5 @@ guide/build/
*.sock
*~
.DS_Store
Server.toml

5
.prettierrc.yml Normal file
View File

@ -0,0 +1,5 @@
overrides:
- files: "*.md"
options:
proseWrap: never
printWidth: 9999

3292
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,31 @@ members = [
"actix-identity",
"actix-limitation",
"actix-protobuf",
"actix-redis",
"actix-session",
"actix-settings",
"actix-web-httpauth",
"actix-ws",
]
[workspace.package]
repository = "https://github.com/actix/actix-extras"
homepage = "https://actix.rs"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.75"
[workspace.lints.rust]
rust-2018-idioms = { level = "deny" }
nonstandard-style = { level = "deny" }
future-incompatible = { level = "deny" }
[patch.crates-io]
actix-cors = { path = "./actix-cors" }
actix-identity = { path = "./actix-identity" }
actix-limitation = { path = "./actix-limitation" }
actix-protobuf = { path = "./actix-protobuf" }
actix-redis = { path = "./actix-redis" }
actix-session = { path = "./actix-session" }
actix-settings = { path = "./actix-settings" }
actix-web-httpauth = { path = "./actix-web-httpauth" }
# uncomment to quickly test against local actix-web repo

View File

@ -186,8 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017-NOW Nikolay Kim
Copyright 2017-NOW svartalf and Actix team
Copyright 2017-NOW Actix team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,4 @@
Copyright (c) 2017 Nikolay Kim
Copyright (c) 2017 svartalf and Actix team
Copyright (c) 2023 Actix team
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated

View File

@ -2,22 +2,27 @@
> A collection of additional crates supporting [Actix Web].
<!-- prettier-ignore-start -->
[![CI](https://github.com/actix/actix-extras/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-extras/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/actix/actix-extras/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-extras)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/5Ux4QGChWc)
[![Dependency Status](https://deps.rs/repo/github/actix/actix-extras/status.svg)](https://deps.rs/repo/github/actix/actix-extras)
<!-- prettier-ignore-end -->
## Crates by @actix
| Crate | | |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| [actix-cors] | [![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors) [![dependency status](https://deps.rs/crate/actix-cors/0.6.1/status.svg)](https://deps.rs/crate/actix-cors/0.6.1) | Cross-origin resource sharing (CORS) for actix-web applications. |
| [actix-identity] | [![crates.io](https://img.shields.io/crates/v/actix-identity?label=latest)](https://crates.io/crates/actix-identity) [![dependency status](https://deps.rs/crate/actix-identity/0.4.0/status.svg)](https://deps.rs/crate/actix-identity/0.4.0) | Identity service for actix-web framework. |
| [actix-limitation] | [![crates.io](https://img.shields.io/crates/v/actix-limitation?label=latest)](https://crates.io/crates/actix-limitation) [![dependency status](https://deps.rs/crate/actix-limitation/0.2.0/status.svg)](https://deps.rs/crate/actix-limitation/0.2.0) | Rate-limiting using a fixed window counter for arbitrary keys, backed by Redis. |
| [actix-protobuf] | [![crates.io](https://img.shields.io/crates/v/actix-protobuf?label=latest)](https://crates.io/crates/actix-protobuf) [![dependency status](https://deps.rs/crate/actix-protobuf/0.7.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.7.0) | Protobuf support for actix-web framework. |
| [actix-redis] | [![crates.io](https://img.shields.io/crates/v/actix-redis?label=latest)](https://crates.io/crates/actix-redis) [![dependency status](https://deps.rs/crate/actix-redis/0.11.0/status.svg)](https://deps.rs/crate/actix-redis/0.11.0) | Redis integration for actix framework. |
| [actix-session] | [![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session) [![dependency status](https://deps.rs/crate/actix-session/0.6.0/status.svg)](https://deps.rs/crate/actix-session/0.6.0) | Session for actix-web framework. |
| [actix-web-httpauth] | [![crates.io](https://img.shields.io/crates/v/actix-web-httpauth?label=latest)](https://crates.io/crates/actix-web-httpauth) [![dependency status](https://deps.rs/crate/actix-web-httpauth/0.6.0/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.6.0) | HTTP authentication schemes for actix-web. |
| Crate | | |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| [actix-cors] | [![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors) [![dependency status](https://deps.rs/crate/actix-cors/latest/status.svg)](https://deps.rs/crate/actix-cors) | Cross-Origin Resource Sharing (CORS) controls. |
| [actix-identity] | [![crates.io](https://img.shields.io/crates/v/actix-identity?label=latest)](https://crates.io/crates/actix-identity) [![dependency status](https://deps.rs/crate/actix-identity/latest/status.svg)](https://deps.rs/crate/actix-identity) | Identity management. |
| [actix-limitation] | [![crates.io](https://img.shields.io/crates/v/actix-limitation?label=latest)](https://crates.io/crates/actix-limitation) [![dependency status](https://deps.rs/crate/actix-limitation/latest/status.svg)](https://deps.rs/crate/actix-limitation) | Rate-limiting using a fixed window counter for arbitrary keys, backed by Redis. |
| [actix-protobuf] | [![crates.io](https://img.shields.io/crates/v/actix-protobuf?label=latest)](https://crates.io/crates/actix-protobuf) [![dependency status](https://deps.rs/crate/actix-protobuf/latest/status.svg)](https://deps.rs/crate/actix-protobuf) | Protobuf payload extractor. |
| [actix-session] | [![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session) [![dependency status](https://deps.rs/crate/actix-session/latest/status.svg)](https://deps.rs/crate/actix-session) | Session management. |
| [actix-settings] | [![crates.io](https://img.shields.io/crates/v/actix-settings?label=latest)](https://crates.io/crates/actix-settings) [![dependency status](https://deps.rs/crate/actix-settings/latest/status.svg)](https://deps.rs/crate/actix-settings) | Easily manage Actix Web's settings from a TOML file and environment variables. |
| [actix-web-httpauth] | [![crates.io](https://img.shields.io/crates/v/actix-web-httpauth?label=latest)](https://crates.io/crates/actix-web-httpauth) [![dependency status](https://deps.rs/crate/actix-web-httpauth/latest/status.svg)](https://deps.rs/crate/actix-web-httpauth) | HTTP authentication schemes. |
| [actix-ws] | [![crates.io](https://img.shields.io/crates/v/actix-ws?label=latest)][actix-ws] [![dependency status](https://deps.rs/crate/actix-ws/latest/status.svg)](https://deps.rs/crate/actix-ws) | WebSockets for Actix Web, without actors. |
---
@ -25,22 +30,27 @@
These crates are provided by the community.
| Crate | | |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [actix-web-lab] | [![crates.io](https://img.shields.io/crates/v/actix-web-lab?label=latest)][actix-web-lab] [![dependency status](https://deps.rs/crate/actix-web-lab/0.16.4/status.svg)](https://deps.rs/crate/actix-web-lab/0.16.4) | Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. |
| [actix-multipart-extract] | [![crates.io](https://img.shields.io/crates/v/actix-multipart-extract?label=latest)][actix-multipart-extract] [![dependency status](https://deps.rs/crate/actix-multipart-extract/0.1.4/status.svg)](https://deps.rs/crate/actix-multipart-extract/0.1.4) | Better multipart form support for Actix Web. |
| [actix-form-data] | [![crates.io](https://img.shields.io/crates/v/actix-form-data?label=latest)][actix-form-data] [![dependency status](https://deps.rs/crate/actix-form-data/0.6.2/status.svg)](https://deps.rs/crate/actix-form-data/0.6.2) | Rate-limiting backed by form-data. |
| [actix-governor] | [![crates.io](https://img.shields.io/crates/v/actix-governor?label=latest)][actix-governor] [![dependency status](https://deps.rs/crate/actix-governor/0.3.0/status.svg)](https://deps.rs/crate/actix-governor/0.3.0) | Rate-limiting backed by governor. |
| [actix-casbin] | [![crates.io](https://img.shields.io/crates/v/actix-casbin?label=latest)][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-ip-filter] | [![crates.io](https://img.shields.io/crates/v/actix-ip-filter?label=latest)][actix-ip-filter] [![dependency status](https://deps.rs/crate/actix-ip-filter/0.3.1/status.svg)](https://deps.rs/crate/actix-ip-filter/0.3.1) | IP address filter. Supports glob patterns. |
| [actix-web-static-files] | [![crates.io](https://img.shields.io/crates/v/actix-web-static-files?label=latest)][actix-web-static-files] [![dependency status](https://deps.rs/crate/actix-web-static-files/4.0.0/status.svg)](https://deps.rs/crate/actix-web-static-files/4.0.0) | Static files as embedded resources. |
| [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)][actix-web-grants] [![dependency status](https://deps.rs/crate/actix-web-grants/3.0.1/status.svg)](https://deps.rs/crate/actix-web-grants/3.0.1) | Extension for validating user authorities. |
| [aliri_actix] | [![crates.io](https://img.shields.io/crates/v/aliri_actix?label=latest)][aliri_actix] [![dependency status](https://deps.rs/crate/aliri_actix/0.7.0/status.svg)](https://deps.rs/crate/aliri_actix/0.7.0) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. |
| [actix-web-flash-messages] | [![crates.io](https://img.shields.io/crates/v/actix-web-flash-messages?label=latest)][actix-web-flash-messages] [![dependency status](https://deps.rs/crate/actix-web-flash-messages/0.4.1/status.svg)](https://deps.rs/crate/actix-web-flash-messages/0.4.1) | Support for flash messages/one-time notifications in `actix-web`. |
| [awmp] | [![crates.io](https://img.shields.io/crates/v/awmp?label=latest)][awmp] [![dependency status](https://deps.rs/crate/awmp/0.8.1/status.svg)](https://deps.rs/crate/awmp/0.8.1) | An easy to use wrapper around multipart fields for Actix Web. |
| [tracing-actix-web] | [![crates.io](https://img.shields.io/crates/v/tracing-actix-web?label=latest)][tracing-actix-web] [![dependency status](https://deps.rs/crate/tracing-actix-web/0.6.0/status.svg)](https://deps.rs/crate/tracing-actix-web/0.6.0) | A middleware to collect telemetry data from applications built on top of the actix-web framework. |
| [actix-ws] | [![crates.io](https://img.shields.io/crates/v/actix-ws?label=latest)][actix-ws] [![dependency status](https://deps.rs/crate/actix-ws/0.2.5/status.svg)](https://deps.rs/crate/actix-ws/0.2.5) | Actor-less WebSockets for the Actix Runtime. |
| [actix-hash] | [![crates.io](https://img.shields.io/crates/v/actix-hash?label=latest)][actix-hash] [![dependency status](https://deps.rs/crate/actix-hash/0.4.0/status.svg)](https://deps.rs/crate/actix-hash/0.4.0) | Hashing utilities for Actix Web. |
| Crate | | |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [actix-web-lab] | [![crates.io](https://img.shields.io/crates/v/actix-web-lab?label=latest)][actix-web-lab] [![dependency status](https://deps.rs/crate/actix-web-lab/latest/status.svg)](https://deps.rs/crate/actix-web-lab) | Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. |
| [actix-form-data] | [![crates.io](https://img.shields.io/crates/v/actix-form-data?label=latest)][actix-form-data] [![dependency status](https://deps.rs/crate/actix-form-data/latest/status.svg)](https://deps.rs/crate/actix-form-data) | Multipart form data from actix multipart streams. |
| [actix-governor] | [![crates.io](https://img.shields.io/crates/v/actix-governor?label=latest)][actix-governor] [![dependency status](https://deps.rs/crate/actix-governor/latest/status.svg)](https://deps.rs/crate/actix-governor) | Rate-limiting backed by governor. |
| [actix-casbin] | [![crates.io](https://img.shields.io/crates/v/actix-casbin?label=latest)][actix-casbin] [![dependency status](https://deps.rs/crate/actix-casbin/latest/status.svg)](https://deps.rs/crate/actix-casbin) | Authorization library that supports access control models like ACL, RBAC & ABAC. |
| [actix-ip-filter] | [![crates.io](https://img.shields.io/crates/v/actix-ip-filter?label=latest)][actix-ip-filter] [![dependency status](https://deps.rs/crate/actix-ip-filter/latest/status.svg)](https://deps.rs/crate/actix-ip-filter) | IP address filter. Supports glob patterns. |
| [actix-web-static-files] | [![crates.io](https://img.shields.io/crates/v/actix-web-static-files?label=latest)][actix-web-static-files] [![dependency status](https://deps.rs/crate/actix-web-static-files/latest/status.svg)](https://deps.rs/crate/actix-web-static-files) | Static files as embedded resources. |
| [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)][actix-web-grants] [![dependency status](https://deps.rs/crate/actix-web-grants/latest/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)][aliri_actix] [![dependency status](https://deps.rs/crate/aliri_actix/latest/status.svg)](https://deps.rs/crate/aliri_actix) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. |
| [actix-web-flash-messages] | [![crates.io](https://img.shields.io/crates/v/actix-web-flash-messages?label=latest)][actix-web-flash-messages] [![dependency status](https://deps.rs/crate/actix-web-flash-messages/latest/status.svg)](https://deps.rs/crate/actix-web-flash-messages) | Support for flash messages/one-time notifications in `actix-web`. |
| [awmp] | [![crates.io](https://img.shields.io/crates/v/awmp?label=latest)][awmp] [![dependency status](https://deps.rs/crate/awmp/latest/status.svg)](https://deps.rs/crate/awmp) | An easy to use wrapper around multipart fields for Actix Web. |
| [tracing-actix-web] | [![crates.io](https://img.shields.io/crates/v/tracing-actix-web?label=latest)][tracing-actix-web] [![dependency status](https://deps.rs/crate/tracing-actix-web/latest/status.svg)](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] | [![crates.io](https://img.shields.io/crates/v/actix-hash?label=latest)][actix-hash] [![dependency status](https://deps.rs/crate/actix-hash/latest/status.svg)](https://deps.rs/crate/actix-hash) | Hashing utilities for Actix Web. |
| [actix-bincode] | ![crates.io](https://img.shields.io/crates/v/actix-bincode?label=latest) [![dependency status](https://deps.rs/crate/actix-bincode/latest/status.svg)](https://deps.rs/crate/actix-bincode) | Bincode payload extractor for Actix Web. |
| [sentinel-actix] | ![crates.io](https://img.shields.io/crates/v/sentinel-actix?label=latest) [![dependency status](https://deps.rs/crate/sentinel-actix/latest/status.svg)](https://deps.rs/crate/sentinel-actix) | General and flexible protection for Actix Web. |
| [actix-telepathy] | ![crates.io](https://img.shields.io/crates/v/actix-telepathy?label=latest) [![dependency status](https://deps.rs/crate/actix-telepathy/latest/status.svg)](https://deps.rs/crate/actix-telepathy) | Build distributed applications with `RemoteActors` and `RemoteMessages`. |
| [apistos] | ![crates.io](https://img.shields.io/crates/v/apistos?label=latest) [![dependency status](https://deps.rs/crate/apistos/latest/status.svg)](https://deps.rs/crate/apistos) | Automatic OpenAPI v3 documentation for Actix Web. |
| [actix-web-validation] | ![crates.io](https://img.shields.io/crates/v/actix-web-validation?label=latest) [![dependency status](https://deps.rs/crate/actix-web-validation/latest/status.svg)](https://deps.rs/crate/actix-web-validation) | Request validation for Actix Web. |
| [actix-jwt-cookies] | ![crates.io](https://img.shields.io/crates/v/actix-jwt-cookies?label=latest) [![dependency status](https://deps.rs/repo/github/Necoo33/actix-jwt-cookies/status.svg)](https://deps.rs/repo/github/Necoo33/actix-jwt-cookies?path=%2F) | Store your data in encrypted cookies and get it elegantly. |
| [actix-ws-broadcaster] | ![crates.io](https://img.shields.io/crates/v/actix-ws-broadcaster?label=latest) [![dependency status](https://deps.rs/repo/github/Necoo33/actix-ws-broadcaster/status.svg?path=%2F)](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.
@ -53,8 +63,8 @@ To add a crate to this list, submit a pull request.
[actix-identity]: ./actix-identity
[actix-limitation]: ./actix-limitation
[actix-protobuf]: ./actix-protobuf
[actix-redis]: ./actix-redis
[actix-session]: ./actix-session
[actix-settings]: ./actix-settings
[actix-web-httpauth]: ./actix-web-httpauth
[actix-web-lab]: https://crates.io/crates/actix-web-lab
[actix-multipart-extract]: https://crates.io/crates/actix-multipart-extract
@ -70,3 +80,11 @@ To add a crate to this list, submit a pull request.
[tracing-actix-web]: https://crates.io/crates/tracing-actix-web
[actix-ws]: https://crates.io/crates/actix-ws
[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://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

View File

@ -1,58 +1,88 @@
# Changes
## Unreleased - 2022-xx-xx
## Unreleased
## 0.7.1
- Implement `PartialEq` for `Cors` allowing for better testing.
## 0.7.0
- `Cors` is now marked `#[must_use]`.
- Default for `Cors::block_on_origin_mismatch` is now false.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.6.5
- Fix `Vary` header when Private Network Access is enabled.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.6.4
- Add `Cors::allow_private_network_access()` behind an unstable flag (`draft-private-network-access`).
## 0.6.3
- Add `Cors::block_on_origin_mismatch()` option for controlling if requests are pre-emptively rejected.
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
## 0.6.2
- Fix `expose_any_header` to return list of response headers.
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.6.1
## 0.6.1 - 2022-03-07
- Do not consider requests without a `Access-Control-Request-Method` as preflight. [#226]
- Do not consider requests without a `Access-Control-Request-Method` as preflight.
[#226]: https://github.com/actix/actix-extras/pull/226
## 0.6.0
## 0.6.0 - 2022-02-25
- Update `actix-web` dependency to 4.0.
## 0.6.0-beta.10 - 2022-02-07
<details>
<summary>0.6.0 pre-releases</summary>
## 0.6.0-beta.10
- Ensure that preflight responses contain a `Vary` header. [#224]
[#224]: https://github.com/actix/actix-extras/pull/224
## 0.6.0-beta.9
## 0.6.0-beta.9 - 2022-02-07
- Relax body type bounds on middleware impl. [#223]
- Update `actix-web` dependency to `4.0.0-rc.1`.
[#223]: https://github.com/actix/actix-extras/pull/223
## 0.6.0-beta.8
## 0.6.0-beta.8 - 2021-12-29
- Minimum supported Rust version (MSRV) is now 1.54.
## 0.6.0-beta.7
## 0.6.0-beta.7 - 2021-12-18
- Update `actix-web` dependency to `4.0.0-beta.15`. [#216]
[#216]: https://github.com/actix/actix-extras/pull/216
## 0.6.0-beta.6
## 0.6.0-beta.6 - 2021-12-13
- Fix panic when wrapping routes with dynamic segments in their paths. [#213]
[#213]: https://github.com/actix/actix-extras/pull/213
## 0.6.0-beta.5 _(YANKED)_
## 0.6.0-beta.5 - 2021-12-12 _(YANKED)_
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.6.0-beta.4
## 0.6.0-beta.4 - 2021-11-22
- No significant changes since `0.6.0-beta.3`.
## 0.6.0-beta.3
## 0.6.0-beta.3 - 2021-10-21
- Make `Cors` middleware generic over body type [#195]
- Fix `expose_any_header` behavior. [#204]
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
@ -62,90 +92,90 @@
[#203]: https://github.com/actix/actix-extras/pull/203
[#204]: https://github.com/actix/actix-extras/pull/204
## 0.6.0-beta.2
## 0.6.0-beta.2 - 2021-06-27
- No notable changes.
## 0.6.0-beta.1
## 0.6.0-beta.1 - 2021-04-02
- Update `actix-web` dependency to 4.0.0 beta.
- Minimum supported Rust version (MSRV) is now 1.46.0.
</details>
## 0.5.4
## 0.5.4 - 2020-12-31
- Fix `expose_any_header` method, now set the correct field. [#143]
[#143]: https://github.com/actix/actix-extras/pull/143
## 0.5.3
## 0.5.3 - 2020-11-19
- Fix version spec for `derive_more` dependency.
## 0.5.2
## 0.5.2 - 2020-11-15
- Ensure `tinyvec` is using the correct features.
- Bump `futures-util` minimum version to `0.3.7` to avoid `RUSTSEC-2020-0059`.
## 0.5.1
## 0.5.1 - 2020-11-05
- Fix `allow_any_header` method, now set the correct field. [#121]
[#121]: https://github.com/actix/actix-extras/pull/121
## 0.5.0
## 0.5.0 - 2020-10-19
- Disallow `*` in `Cors::allowed_origin`. [#114].
- Hide `CorsMiddleware` from docs. [#118].
- `CorsFactory` is removed. [#119]
- The `impl Default` constructor is now overly-restrictive. [#119]
- Added `Cors::permissive()` constructor that allows anything. [#119]
- Adds methods for each property to reset to a permissive state. (`allow_any_origin`,
`expose_any_header`, etc.) [#119]
- Adds methods for each property to reset to a permissive state. (`allow_any_origin`, `expose_any_header`, etc.) [#119]
- Errors are now propagated with `Transform::InitError` instead of panicking. [#119]
- Fixes bug where allowed origin functions are not called if `allowed_origins` is All. [#119]
- `AllOrSome` is no longer public. [#119]
- Functions used for `allowed_origin_fn` now receive the Origin HeaderValue as the
first parameter. [#120]
- Functions used for `allowed_origin_fn` now receive the Origin HeaderValue as the first parameter. [#120]
[#114]: https://github.com/actix/actix-extras/pull/114
[#118]: https://github.com/actix/actix-extras/pull/118
[#119]: https://github.com/actix/actix-extras/pull/119
[#120]: https://github.com/actix/actix-extras/pull/120
## 0.4.1
## 0.4.1 - 2020-10-07
- Allow closures to be used with `allowed_origin_fn`. [#110]
[#110]: https://github.com/actix/actix-extras/pull/110
## 0.4.0
## 0.4.0 - 2020-09-27
- Implement `allowed_origin_fn` builder method. [#93]
- Use `TryInto` instead of `TryFrom` where applicable. [#106]
[#93]: https://github.com/actix/actix-extras/pull/93
[#106]: https://github.com/actix/actix-extras/pull/106
## 0.3.0
## 0.3.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimum supported Rust version (MSRV) is now 1.42.0.
- Implement the Debug trait on all public types.
## 0.3.0-alpha.1
## 0.3.0-alpha.1 - 2020-03-11
- Minimize `futures-*` dependencies
- Update `actix-web` dependency to 3.0.0-alpha.1
## 0.2.0 - 2019-12-20
- Release
## 0.2.0-alpha.3 - 2019-12-07
- Migrate to actix-web 2.0.0
- Bump `derive_more` crate version to 0.99.0
## 0.1.0 - 2019-06-15
- Move cors middleware to separate crate

View File

@ -1,32 +1,39 @@
[package]
name = "actix-cors"
version = "0.6.1"
version = "0.7.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Cross-Origin Resource Sharing (CORS) controls for Actix Web"
keywords = ["actix", "cors", "web", "security", "crossorigin"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
license = "MIT OR Apache-2.0"
edition = "2018"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
name = "actix_cors"
path = "src/lib.rs"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[features]
draft-private-network-access = []
[dependencies]
actix-utils = "3"
actix-web = { version = "4", default-features = false }
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false }
derive_more = { version = "2", features = ["display", "error"] }
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
log = "0.4"
once_cell = "1"
smallvec = "1.6.1"
smallvec = "1"
[dev-dependencies]
actix-web = { version = "4", default_features = false, features = ["macros"] }
env_logger = "0.9"
actix-web = { version = "4", default-features = false, features = ["macros"] }
env_logger = "0.11"
regex = "1.4"
[lints]
workspace = true

View File

@ -1,14 +1,72 @@
# actix-cors
> Cross-origin resource sharing (CORS) for Actix Web.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors)
[![Documentation](https://docs.rs/actix-cors/badge.svg?version=0.6.1)](https://docs.rs/actix-cors/0.6.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-cors)
[![Dependency Status](https://deps.rs/crate/actix-cors/0.6.1/status.svg)](https://deps.rs/crate/actix-cors/0.6.1)
[![Documentation](https://docs.rs/actix-cors/badge.svg?version=0.7.1)](https://docs.rs/actix-cors/0.7.1)
![Version](https://img.shields.io/badge/rustc-1.75+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-cors.svg)
<br />
[![Dependency Status](https://deps.rs/crate/actix-cors/0.7.1/status.svg)](https://deps.rs/crate/actix-cors/0.7.1)
[![Download](https://img.shields.io/crates/d/actix-cors.svg)](https://crates.io/crates/actix-cors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
<!-- cargo-rdme start -->
Cross-Origin Resource Sharing (CORS) controls for Actix Web.
This middleware can be applied to both applications and resources. Once built, a [`Cors`] builder can be used as an argument for Actix Web's `App::wrap()`, `Scope::wrap()`, or `Resource::wrap()` methods.
This CORS middleware automatically handles `OPTIONS` preflight requests.
## Crate Features
- `draft-private-network-access`: ⚠️ Unstable. Adds opt-in support for the [Private Network Access] spec extensions. This feature is unstable since it will follow breaking changes in the draft spec until it is finalized.
## Example
```rust
use actix_cors::Cors;
use actix_web::{get, http, web, App, HttpRequest, HttpResponse, HttpServer};
#[get("/index.html")]
async fn index(req: HttpRequest) -> &'static str {
"<p>Hello World!</p>"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let cors = Cors::default()
.allowed_origin("https://www.rust-lang.org")
.allowed_origin_fn(|origin, _req_head| {
origin.as_bytes().ends_with(b".rust-lang.org")
})
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.wrap(cors)
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await;
Ok(())
}
```
[Private Network Access]: https://wicg.github.io/private-network-access
<!-- cargo-rdme end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-cors)
- [Example Project](https://github.com/actix/examples/tree/master/cors)
- Minimum Supported Rust Version (MSRV): 1.57
- Minimum Supported Rust Version (MSRV): 1.75

View File

@ -39,6 +39,8 @@ async fn main() -> std::io::Result<()> {
.allowed_header(header::CONTENT_TYPE)
// set list of headers that are safe to expose
.expose_headers(&[header::CONTENT_DISPOSITION])
// allow cURL/HTTPie from working without providing Origin headers
.block_on_origin_mismatch(false)
// set preflight cache TTL
.max_age(3600),
)

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, convert::TryInto, iter::FromIterator, rc::Rc};
use std::{collections::HashSet, rc::Rc};
use actix_utils::future::{self, Ready};
use actix_web::{
@ -52,13 +52,20 @@ static ALL_METHODS_SET: Lazy<HashSet<Method>> = Lazy::new(|| {
/// The alternative [`Cors::permissive()`] constructor is available for local development, allowing
/// all origins and headers, etc. **The permissive constructor should not be used in production.**
///
/// # Behavior
///
/// In all cases, behavior for this crate follows the [Fetch Standard CORS protocol]. See that
/// document for information on exact semantics for configuration options and combinations.
///
/// # Errors
///
/// Errors surface in the middleware initialization phase. This means that, if you have logs enabled
/// in Actix Web (using `env_logger` or other crate that exposes logs from the `log` crate), error
/// messages will outline what is wrong with the CORS configuration in the server logs and the
/// server will fail to start up or serve requests.
///
/// # Example
///
/// ```
/// use actix_cors::Cors;
/// use actix_web::http::header;
@ -72,14 +79,18 @@ static ALL_METHODS_SET: Lazy<HashSet<Method>> = Lazy::new(|| {
///
/// // `cors` can now be used in `App::wrap`.
/// ```
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
#[derive(Debug)]
#[must_use]
pub struct Cors {
inner: Rc<Inner>,
error: Option<Either<HttpError, CorsError>>,
}
impl Cors {
/// A very permissive set of default for quick development. Not recommended for production use.
/// Constructs a very permissive set of defaults for quick development. (Not recommended for
/// production use.)
///
/// *All* origins, methods, request headers and exposed headers allowed. Credentials supported.
/// Max age 1 hour. Does not send wildcard.
@ -101,7 +112,10 @@ impl Cors {
preflight: true,
send_wildcard: false,
supports_credentials: true,
#[cfg(feature = "draft-private-network-access")]
allow_private_network_access: false,
vary_header: true,
block_on_origin_mismatch: false,
};
Cors {
@ -121,12 +135,12 @@ impl Cors {
self
}
/// Add an origin that is allowed to make requests.
/// Adds an origin that is allowed to make requests.
///
/// By default, requests from all origins are accepted by CORS logic. This method allows to
/// specify a finite set of origins to verify the value of the `Origin` request header.
/// This method allows specifying a finite set of origins to verify the value of the `Origin`
/// request header. These are `origin-or-null` types in the [Fetch Standard].
///
/// These are `origin-or-null` types in the [Fetch Standard].
/// By default, no origins are accepted.
///
/// When this list is set, the client's `Origin` request header will be checked in a
/// case-sensitive manner.
@ -174,7 +188,7 @@ impl Cors {
self
}
/// Determinate allowed origins by processing requests which didn't match any origins specified
/// Determinates allowed origins by processing requests which didn't match any origins specified
/// in the `allowed_origin`.
///
/// The function will receive two parameters, the Origin header value, and the `RequestHead` of
@ -200,20 +214,17 @@ impl Cors {
/// See [`Cors::allowed_methods`] for more info on allowed methods.
pub fn allow_any_method(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.allowed_methods = ALL_METHODS_SET.clone();
ALL_METHODS_SET.clone_into(&mut cors.allowed_methods);
}
self
}
/// Set a list of methods which allowed origins can perform.
/// Sets a list of methods which allowed origins can perform.
///
/// These will be sent in the `Access-Control-Allow-Methods` response header as specified in
/// the [Fetch Standard CORS protocol].
/// These will be sent in the `Access-Control-Allow-Methods` response header.
///
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
/// This defaults to an empty set.
pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors
where
U: IntoIterator<Item = M>,
@ -276,16 +287,13 @@ impl Cors {
self
}
/// Set a list of request header field names which can be used when this resource is accessed by
/// allowed origins.
/// Sets a list of request header field names which can be used when this resource is accessed
/// by allowed origins.
///
/// If `All` is set, whatever is requested by the client in `Access-Control-Request-Headers`
/// will be echoed back in the `Access-Control-Allow-Headers` header as specified in
/// the [Fetch Standard CORS protocol].
/// will be echoed back in the `Access-Control-Allow-Headers` header.
///
/// Defaults to `All`.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
/// This defaults to an empty set.
pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors
where
U: IntoIterator<Item = H>,
@ -315,7 +323,7 @@ impl Cors {
self
}
/// Resets exposed response header list to a state where any header is accepted.
/// Resets exposed response header list to a state where all headers are exposed.
///
/// See [`Cors::expose_headers`] for more info on exposed response headers.
pub fn expose_any_header(mut self) -> Cors {
@ -326,13 +334,11 @@ impl Cors {
self
}
/// Set a list of headers which are safe to expose to the API of a CORS API specification.
/// This corresponds to the `Access-Control-Expose-Headers` response header as specified in
/// the [Fetch Standard CORS protocol].
/// Sets a list of headers which are safe to expose to the API of a CORS API specification.
///
/// This corresponds to the `Access-Control-Expose-Headers` response header.
///
/// This defaults to an empty set.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn expose_headers<U, H>(mut self, headers: U) -> Cors
where
U: IntoIterator<Item = H>,
@ -361,63 +367,76 @@ impl Cors {
self
}
/// Set a maximum time (in seconds) for which this CORS request may be cached. This value is set
/// as the `Access-Control-Max-Age` header as specified in the [Fetch Standard CORS protocol].
/// Sets a maximum time (in seconds) for which this CORS request may be cached.
///
/// This value is set as the `Access-Control-Max-Age` header.
///
/// Pass a number (of seconds) or use None to disable sending max age header.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn max_age(mut self, max_age: impl Into<Option<usize>>) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.max_age = max_age.into()
cors.max_age = max_age.into();
}
self
}
/// Set to use wildcard origins.
/// Configures use of wildcard (`*`) origin in responses when appropriate.
///
/// If send wildcard is set and the `allowed_origins` parameter is `All`, a wildcard
/// `Access-Control-Allow-Origin` response header is sent, rather than the requests
/// `Origin` header.
///
/// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and
/// `allow_credentials` set to `true`. Depending on the mode of usage, this will either result
/// in an `CorsError::CredentialsWithWildcardOrigin` error during actix launch or runtime.
/// This option **CANNOT** be used in conjunction with a [credential
/// supported](Self::supports_credentials()) configuration. Doing so will result in an error
/// during server startup.
///
/// Defaults to `false`.
/// Defaults to disabled.
pub fn send_wildcard(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.send_wildcard = true
cors.send_wildcard = true;
}
self
}
/// Allows users to make authenticated requests
/// Allows users to make authenticated requests.
///
/// If true, injects the `Access-Control-Allow-Credentials` header in responses. This allows
/// cookies and credentials to be submitted across domains as specified in
/// the [Fetch Standard CORS protocol].
/// cookies and credentials to be submitted across domains.
///
/// This option cannot be used in conjunction with an `allowed_origin` set to `All` and
/// `send_wildcards` set to `true`.
/// This option **CANNOT** be used in conjunction with option cannot be used in conjunction
/// with [wildcard origins](Self::send_wildcard()) configured. Doing so will result in an error
/// during server startup.
///
/// Defaults to disabled.
pub fn supports_credentials(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.supports_credentials = true;
}
self
}
/// Allow private network access.
///
/// If true, injects the `Access-Control-Allow-Private-Network: true` header in responses if the
/// request contained the `Access-Control-Request-Private-Network: true` header.
///
/// For more information on this behavior, see the draft [Private Network Access] spec.
///
/// Defaults to `false`.
///
/// A server initialization error will occur if credentials are allowed, but the Origin is set
/// to send wildcards (`*`); this is not allowed by the CORS protocol.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn supports_credentials(mut self) -> Cors {
/// [Private Network Access]: https://wicg.github.io/private-network-access
#[cfg(feature = "draft-private-network-access")]
pub fn allow_private_network_access(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.supports_credentials = true
cors.allow_private_network_access = true;
}
self
}
/// Disable `Vary` header support.
/// Disables `Vary` header support.
///
/// When enabled the header `Vary: Origin` will be returned as per the Fetch Standard
/// implementation guidelines.
@ -429,21 +448,39 @@ impl Cors {
/// By default, `Vary` header support is enabled.
pub fn disable_vary_header(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.vary_header = false
cors.vary_header = false;
}
self
}
/// Disable support for preflight requests.
/// Disables preflight request handling.
///
/// When enabled CORS middleware automatically handles `OPTIONS` requests.
/// This is useful for application level middleware.
/// When enabled CORS middleware automatically handles `OPTIONS` requests. This is useful for
/// application level middleware.
///
/// By default *preflight* support is enabled.
/// By default, preflight support is enabled.
pub fn disable_preflight(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.preflight = false
cors.preflight = false;
}
self
}
/// Configures whether requests should be pre-emptively blocked on mismatched origin.
///
/// If `true`, a 400 Bad Request is returned immediately when a request fails origin validation.
///
/// If `false`, the request will be processed as normal but relevant CORS headers will not be
/// appended to the response. In this case, the browser is trusted to validate CORS headers and
/// and block requests based on pre-flight requests. Use this setting to allow cURL and other
/// non-browser HTTP clients to function as normal, no matter what `Origin` the request has.
///
/// Defaults to false.
pub fn block_on_origin_mismatch(mut self, block: bool) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.block_on_origin_mismatch = block;
}
self
@ -473,7 +510,10 @@ impl Default for Cors {
preflight: true,
send_wildcard: false,
supports_credentials: false,
#[cfg(feature = "draft-private-network-access")]
allow_private_network_access: false,
vary_header: true,
block_on_origin_mismatch: false,
};
Cors {
@ -568,14 +608,27 @@ 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, TryInto};
use std::convert::Infallible;
use actix_web::{
body,
dev::{fn_service, Transform},
http::{header::HeaderName, StatusCode},
dev::fn_service,
http::StatusCode,
test::{self, TestRequest},
HttpResponse,
};
@ -606,8 +659,9 @@ mod test {
.insert_header(("Origin", "https://www.example.com"))
.to_srv_request();
let resp = test::call_service(&cors, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let res = test::call_service(&cors, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key("Access-Control-Allow-Origin"));
}
#[actix_web::test]
@ -638,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());
}
}

View File

@ -1,40 +1,40 @@
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use derive_more::{Display, Error};
use derive_more::derive::{Display, Error};
/// Errors that can occur when processing CORS guarded requests.
#[derive(Debug, Clone, Display, Error)]
#[non_exhaustive]
pub enum CorsError {
/// Allowed origin argument must not be wildcard (`*`).
#[display(fmt = "`allowed_origin` argument must not be wildcard (`*`)")]
#[display("`allowed_origin` argument must not be wildcard (`*`)")]
WildcardOrigin,
/// Request header `Origin` is required but was not provided.
#[display(fmt = "Request header `Origin` is required but was not provided")]
#[display("Request header `Origin` is required but was not provided")]
MissingOrigin,
/// Request header `Access-Control-Request-Method` is required but is missing.
#[display(fmt = "Request header `Access-Control-Request-Method` is required but is missing")]
#[display("Request header `Access-Control-Request-Method` is required but is missing")]
MissingRequestMethod,
/// Request header `Access-Control-Request-Method` has an invalid value.
#[display(fmt = "Request header `Access-Control-Request-Method` has an invalid value")]
#[display("Request header `Access-Control-Request-Method` has an invalid value")]
BadRequestMethod,
/// Request header `Access-Control-Request-Headers` has an invalid value.
#[display(fmt = "Request header `Access-Control-Request-Headers` has an invalid value")]
#[display("Request header `Access-Control-Request-Headers` has an invalid value")]
BadRequestHeaders,
/// Origin is not allowed to make this request.
#[display(fmt = "Origin is not allowed to make this request")]
#[display("Origin is not allowed to make this request")]
OriginNotAllowed,
/// Request method is not allowed.
#[display(fmt = "Requested method is not allowed")]
#[display("Requested method is not allowed")]
MethodNotAllowed,
/// One or more request headers are not allowed.
#[display(fmt = "One or more request headers are not allowed")]
#[display("One or more request headers are not allowed")]
HeadersNotAllowed,
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, convert::TryFrom, convert::TryInto, fmt, rc::Rc};
use std::{collections::HashSet, fmt, rc::Rc};
use actix_web::{
dev::RequestHead,
@ -15,6 +15,7 @@ use crate::{AllOrSome, CorsError};
#[derive(Clone)]
pub(crate) struct OriginFn {
#[allow(clippy::type_complexity)]
pub(crate) boxed_fn: Rc<dyn Fn(&HeaderValue, &RequestHead) -> bool>,
}
@ -26,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")
@ -39,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]>,
@ -58,17 +65,22 @@ pub(crate) struct Inner {
pub(crate) preflight: bool,
pub(crate) send_wildcard: bool,
pub(crate) supports_credentials: bool,
#[cfg(feature = "draft-private-network-access")]
pub(crate) allow_private_network_access: bool,
pub(crate) vary_header: bool,
pub(crate) block_on_origin_mismatch: bool,
}
static EMPTY_ORIGIN_SET: Lazy<HashSet<HeaderValue>> = Lazy::new(HashSet::new);
impl Inner {
pub(crate) fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
/// The bool returned in Ok(_) position indicates whether the `Access-Control-Allow-Origin`
/// header should be added to the response or not.
pub(crate) fn validate_origin(&self, req: &RequestHead) -> Result<bool, CorsError> {
// return early if all origins are allowed or get ref to allowed origins set
#[allow(clippy::mutable_key_type)]
let allowed_origins = match &self.allowed_origins {
AllOrSome::All if self.allowed_origins_fns.is_empty() => return Ok(()),
AllOrSome::All if self.allowed_origins_fns.is_empty() => return Ok(true),
AllOrSome::Some(allowed_origins) => allowed_origins,
// only function origin validators are defined
_ => &EMPTY_ORIGIN_SET,
@ -79,9 +91,11 @@ impl Inner {
// origin header exists and is a string
Some(origin) => {
if allowed_origins.contains(origin) || self.validate_origin_fns(origin, req) {
Ok(())
} else {
Ok(true)
} else if self.block_on_origin_mismatch {
Err(CorsError::OriginNotAllowed)
} else {
Ok(false)
}
}
@ -208,8 +222,20 @@ pub(crate) fn add_vary_header(headers: &mut HeaderMap) {
let mut val: Vec<u8> = Vec::with_capacity(hdr.len() + 71);
val.extend(hdr.as_bytes());
val.extend(b", Origin, Access-Control-Request-Method, Access-Control-Request-Headers");
#[cfg(feature = "draft-private-network-access")]
val.extend(b", Access-Control-Request-Private-Network");
val.try_into().unwrap()
}
#[cfg(feature = "draft-private-network-access")]
None => HeaderValue::from_static(
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, \
Access-Control-Request-Private-Network",
),
#[cfg(not(feature = "draft-private-network-access"))]
None => HeaderValue::from_static(
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
),
@ -241,6 +267,7 @@ mod test {
async fn test_validate_not_allowed_origin() {
let cors = Cors::default()
.allowed_origin("https://www.example.com")
.block_on_origin_mismatch(true)
.new_transform(test::ok_service())
.await
.unwrap();

View File

@ -1,11 +1,16 @@
//! Cross-Origin Resource Sharing (CORS) controls for Actix Web.
//!
//! This middleware can be applied to both applications and resources. Once built, a
//! [`Cors`] builder can be used as an argument for Actix Web's `App::wrap()`,
//! `Scope::wrap()`, or `Resource::wrap()` methods.
//! This middleware can be applied to both applications and resources. Once built, a [`Cors`]
//! builder can be used as an argument for Actix Web's `App::wrap()`, `Scope::wrap()`, or
//! `Resource::wrap()` methods.
//!
//! This CORS middleware automatically handles `OPTIONS` preflight requests.
//!
//! # Crate Features
//! - `draft-private-network-access`: ⚠️ Unstable. Adds opt-in support for the [Private Network
//! Access] spec extensions. This feature is unstable since it will follow breaking changes in the
//! draft spec until it is finalized.
//!
//! # Example
//! ```no_run
//! use actix_cors::Cors;
@ -20,14 +25,14 @@
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| {
//! let cors = Cors::default()
//! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_origin_fn(|origin, _req_head| {
//! origin.as_bytes().ends_with(b".rust-lang.org")
//! })
//! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
//! .allowed_header(http::header::CONTENT_TYPE)
//! .max_age(3600);
//! .allowed_origin("https://www.rust-lang.org")
//! .allowed_origin_fn(|origin, _req_head| {
//! origin.as_bytes().ends_with(b".rust-lang.org")
//! })
//! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
//! .allowed_header(http::header::CONTENT_TYPE)
//! .max_age(3600);
//!
//! App::new()
//! .wrap(cors)
@ -40,12 +45,14 @@
//! Ok(())
//! }
//! ```
//!
//! [Private Network Access]: https://wicg.github.io/private-network-access
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod all_or_some;
mod builder;
@ -53,8 +60,8 @@ mod error;
mod inner;
mod middleware;
use all_or_some::AllOrSome;
pub use builder::Cors;
pub use error::CorsError;
use inner::{Inner, OriginFn};
pub use middleware::CorsMiddleware;
use crate::{
all_or_some::AllOrSome,
inner::{Inner, OriginFn},
};
pub use crate::{builder::Cors, error::CorsError, middleware::CorsMiddleware};

View File

@ -16,7 +16,7 @@ use log::debug;
use crate::{
builder::intersperse_header_values,
inner::{add_vary_header, header_value_try_into_method},
AllOrSome, Inner,
AllOrSome, CorsError, Inner,
};
/// Service wrapper for Cross-Origin Resource Sharing support.
@ -60,9 +60,14 @@ impl<S> CorsMiddleware<S> {
fn handle_preflight(&self, req: ServiceRequest) -> ServiceResponse {
let inner = Rc::clone(&self.inner);
match inner.validate_origin(req.head()) {
Ok(true) => {}
Ok(false) => return req.error_response(CorsError::OriginNotAllowed),
Err(err) => return req.error_response(err),
};
if let Err(err) = inner
.validate_origin(req.head())
.and_then(|_| inner.validate_allowed_method(req.head()))
.validate_allowed_method(req.head())
.and_then(|_| inner.validate_allowed_headers(req.head()))
{
return req.error_response(err);
@ -88,6 +93,18 @@ impl<S> CorsMiddleware<S> {
res.insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, headers.clone()));
}
#[cfg(feature = "draft-private-network-access")]
if inner.allow_private_network_access
&& req
.headers()
.contains_key("access-control-request-private-network")
{
res.insert_header((
header::HeaderName::from_static("access-control-allow-private-network"),
HeaderValue::from_static("true"),
));
}
if inner.supports_credentials {
res.insert_header((
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
@ -108,11 +125,17 @@ impl<S> CorsMiddleware<S> {
req.into_response(res)
}
fn augment_response<B>(inner: &Inner, mut res: ServiceResponse<B>) -> ServiceResponse<B> {
if let Some(origin) = inner.access_control_allow_origin(res.request().head()) {
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
};
fn augment_response<B>(
inner: &Inner,
origin_allowed: bool,
mut res: ServiceResponse<B>,
) -> ServiceResponse<B> {
if origin_allowed {
if let Some(origin) = inner.access_control_allow_origin(res.request().head()) {
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
};
}
if let Some(ref expose) = inner.expose_headers_baked {
log::trace!("exposing selected headers: {:?}", expose);
@ -121,13 +144,11 @@ impl<S> CorsMiddleware<S> {
.insert(header::ACCESS_CONTROL_EXPOSE_HEADERS, expose.clone());
} else if matches!(inner.expose_headers, AllOrSome::All) {
// intersperse_header_values requires that argument is non-empty
if !res.request().headers().is_empty() {
if !res.headers().is_empty() {
// extract header names from request
let expose_all_request_headers = res
.request()
.headers()
.keys()
.into_iter()
.map(|name| name.as_str())
.collect::<HashSet<_>>();
@ -152,6 +173,19 @@ impl<S> CorsMiddleware<S> {
);
}
#[cfg(feature = "draft-private-network-access")]
if inner.allow_private_network_access
&& res
.request()
.headers()
.contains_key("access-control-request-private-network")
{
res.headers_mut().insert(
header::HeaderName::from_static("access-control-allow-private-network"),
HeaderValue::from_static("true"),
);
}
if inner.vary_header {
add_vary_header(res.headers_mut());
}
@ -183,8 +217,10 @@ where
}
// only check actual requests with a origin header
if origin.is_some() {
if let Err(err) = self.inner.validate_origin(req.head()) {
let origin_allowed = match (origin, self.inner.validate_origin(req.head())) {
(None, _) => false,
(_, Ok(origin_allowed)) => origin_allowed,
(_, Err(err)) => {
debug!("origin validation failed; inner service is not called");
let mut res = req.error_response(err);
@ -194,14 +230,14 @@ where
return ok(res.map_into_right_body()).boxed_local();
}
}
};
let inner = Rc::clone(&self.inner);
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await;
Ok(Self::augment_response(&inner, res?).map_into_left_body())
Ok(Self::augment_response(&inner, origin_allowed, res?).map_into_left_body())
})
}
}

View File

@ -1,8 +1,7 @@
use actix_cors::Cors;
use actix_utils::future::ok;
use actix_web::dev::fn_service;
use actix_web::{
dev::{ServiceRequest, Transform},
dev::{fn_service, ServiceRequest, Transform},
http::{
header::{self, HeaderValue},
Method, StatusCode,
@ -265,10 +264,16 @@ async fn test_response() {
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.map(HeaderValue::as_bytes)
);
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
resp.headers().get(header::VARY).map(HeaderValue::as_bytes),
Some(&b"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"[..]),
);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
resp.headers().get(header::VARY).map(HeaderValue::as_bytes),
Some(&b"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"[..]),
);
#[allow(clippy::needless_collect)]
{
@ -312,9 +317,18 @@ async fn test_response() {
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&cors, req).await;
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
resp.headers().get(header::VARY).map(HeaderValue::as_bytes),
Some(&b"Accept, Origin, Access-Control-Request-Method, Access-Control-Request-Headers"[..]),
resp.headers()
.get(header::VARY)
.map(HeaderValue::as_bytes)
.unwrap(),
b"Accept, Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
resp.headers().get(header::VARY).map(HeaderValue::as_bytes).unwrap(),
b"Accept, Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
);
let cors = Cors::default()
@ -355,6 +369,55 @@ async fn test_validate_origin() {
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_web::test]
async fn test_blocks_mismatched_origin_by_default() {
let cors = Cors::default()
.allowed_origin("https://www.example.com")
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::get()
.insert_header(("Origin", "https://www.example.test"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert!(!res
.headers()
.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN));
assert!(!res
.headers()
.contains_key(header::ACCESS_CONTROL_ALLOW_METHODS));
}
#[actix_web::test]
async fn test_mismatched_origin_block_turned_off() {
let cors = Cors::default()
.allow_any_method()
.allowed_origin("https://www.example.com")
.block_on_origin_mismatch(false)
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::default()
.method(Method::OPTIONS)
.insert_header(("Origin", "https://wrong.com"))
.insert_header(("Access-Control-Request-Method", "POST"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN), None);
let req = TestRequest::get()
.insert_header(("Origin", "https://wrong.com"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN), None);
}
#[actix_web::test]
async fn test_no_origin_response() {
let cors = Cors::permissive()
@ -416,6 +479,7 @@ async fn vary_header_on_all_handled_responses() {
assert!(resp
.headers()
.contains_key(header::ACCESS_CONTROL_ALLOW_METHODS));
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
resp.headers()
.get(header::VARY)
@ -424,6 +488,15 @@ async fn vary_header_on_all_handled_responses() {
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
resp.headers()
.get(header::VARY)
.expect("response should have Vary header")
.to_str()
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
);
// follow-up regular request
let req = TestRequest::default()
@ -432,6 +505,7 @@ async fn vary_header_on_all_handled_responses() {
.to_srv_request();
let resp = test::call_service(&cors, req).await;
assert_eq!(resp.status(), StatusCode::OK);
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
resp.headers()
.get(header::VARY)
@ -440,6 +514,15 @@ async fn vary_header_on_all_handled_responses() {
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
resp.headers()
.get(header::VARY)
.expect("response should have Vary header")
.to_str()
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
);
let cors = Cors::default()
.allow_any_method()
@ -447,13 +530,44 @@ async fn vary_header_on_all_handled_responses() {
.await
.unwrap();
// regular request bad origin
// regular request OK with no CORS response headers
let req = TestRequest::default()
.method(Method::PUT)
.insert_header((header::ORIGIN, "https://www.example.com"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert!(!res
.headers()
.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN));
assert!(!res
.headers()
.contains_key(header::ACCESS_CONTROL_ALLOW_METHODS));
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
res.headers()
.get(header::VARY)
.expect("response should have Vary header")
.to_str()
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
res.headers()
.get(header::VARY)
.expect("response should have Vary header")
.to_str()
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
);
// regular request no origin
let req = TestRequest::default().method(Method::PUT).to_srv_request();
let resp = test::call_service(&cors, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
assert_eq!(resp.status(), StatusCode::OK);
#[cfg(not(feature = "draft-private-network-access"))]
assert_eq!(
resp.headers()
.get(header::VARY)
@ -462,18 +576,14 @@ async fn vary_header_on_all_handled_responses() {
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
);
// regular request no origin
let req = TestRequest::default().method(Method::PUT).to_srv_request();
let resp = test::call_service(&cors, req).await;
assert_eq!(resp.status(), StatusCode::OK);
#[cfg(feature = "draft-private-network-access")]
assert_eq!(
resp.headers()
.get(header::VARY)
.expect("response should have Vary header")
.to_str()
.unwrap(),
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
);
}
@ -501,7 +611,15 @@ async fn test_allow_any_origin_any_method_any_header() {
#[actix_web::test]
async fn expose_all_request_header_values() {
let cors = Cors::permissive()
.new_transform(test::ok_service())
.new_transform(fn_service(|req: ServiceRequest| async move {
let res = req.into_response(
HttpResponse::Ok()
.insert_header((header::CONTENT_DISPOSITION, "test disposition"))
.finish(),
);
Ok(res)
}))
.await
.unwrap();
@ -509,20 +627,56 @@ async fn expose_all_request_header_values() {
.insert_header((header::ORIGIN, "https://www.example.com"))
.insert_header((header::ACCESS_CONTROL_REQUEST_METHOD, "POST"))
.insert_header((header::ACCESS_CONTROL_REQUEST_HEADERS, "content-type"))
.insert_header(("X-XSRF-TOKEN", "xsrf-token"))
.to_srv_request();
let resp = test::call_service(&cors, req).await;
let res = test::call_service(&cors, req).await;
assert!(resp
.headers()
.contains_key(header::ACCESS_CONTROL_EXPOSE_HEADERS));
assert!(resp
let cd_hdr = res
.headers()
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str()
.unwrap()
.contains("xsrf-token"));
.unwrap();
assert!(cd_hdr.contains("content-disposition"));
assert!(cd_hdr.contains("access-control-allow-origin"));
}
#[cfg(feature = "draft-private-network-access")]
#[actix_web::test]
async fn private_network_access() {
let cors = Cors::permissive()
.allowed_origin("https://public.site")
.allow_private_network_access()
.new_transform(fn_service(|req: ServiceRequest| async move {
let res = req.into_response(
HttpResponse::Ok()
.insert_header((header::CONTENT_DISPOSITION, "test disposition"))
.finish(),
);
Ok(res)
}))
.await
.unwrap();
let req = TestRequest::default()
.insert_header((header::ORIGIN, "https://public.site"))
.insert_header((header::ACCESS_CONTROL_REQUEST_METHOD, "POST"))
.insert_header((header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert!(res.headers().contains_key("access-control-allow-origin"));
let req = TestRequest::default()
.insert_header((header::ORIGIN, "https://public.site"))
.insert_header((header::ACCESS_CONTROL_REQUEST_METHOD, "POST"))
.insert_header((header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"))
.insert_header(("Access-Control-Request-Private-Network", "true"))
.to_srv_request();
let res = test::call_service(&cors, req).await;
assert!(res.headers().contains_key("access-control-allow-origin"));
assert!(res
.headers()
.contains_key("access-control-allow-private-network"));
}

View File

@ -1,59 +1,126 @@
# Changes
## Unreleased - 2022-xx-xx
## Unreleased
## 0.8.0
- Update `actix-session` dependency to `0.10`.
## 0.7.1
- Add `IdentityMiddlewareBuilder::{id_key, last_visit_unix_timestamp_key, login_unix_timestamp_key}()` methods for customizing keys used in session. Defaults remain the same as before.
## 0.7.0
- Update `actix-session` dependency to `0.9`.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.6.0
- Add `error` module.
- Replace use of `anyhow::Error` in return types with specific error types.
- Update `actix-session` dependency to `0.8`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.5.2
- Fix visit deadline. [#263]
[#263]: https://github.com/actix/actix-extras/pull/263
## 0.5.1
- Remove unnecessary dependencies. [#259]
[#259]: https://github.com/actix/actix-extras/pull/259
## 0.5.0
`actix-identity` v0.5 is a complete rewrite. The goal is to streamline user experience and reduce maintenance overhead.
`actix-identity` is now designed as an additional layer on top of `actix-session` v0.7, focused on identity management. The identity information is stored in the session state, which is managed by `actix-session` and can be stored using any of the supported `SessionStore` implementations. This reduces the surface area in `actix-identity` (e.g., it is no longer concerned with cookies!) and provides a smooth upgrade path for users: if you need to work with sessions, you no longer need to choose between `actix-session` and `actix-identity`; they work together now!
`actix-identity` v0.5 has feature-parity with `actix-identity` v0.4; if you bump into any blocker when upgrading, please open an issue.
Changes:
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
- `IdentityService`, `IdentityPolicy` and `CookieIdentityPolicy` have been replaced by `IdentityMiddleware`. [#246]
- Rename `RequestIdentity` trait to `IdentityExt`. [#246]
- Trying to extract an `Identity` for an unauthenticated user will return a `401 Unauthorized` response to the client. Extract an `Option<Identity>` or a `Result<Identity, actix_web::Error>` if you need to handle cases where requests may or may not be authenticated. [#246]
Example:
```rust
use actix_web::{http::header::LOCATION, get, HttpResponse, Responder};
use actix_identity::Identity;
#[get("/")]
async fn index(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
HttpResponse::Ok().finish()
} else {
// Redirect to login page if unauthenticated
HttpResponse::TemporaryRedirect()
.insert_header((LOCATION, "/login"))
.finish()
}
}
```
[#246]: https://github.com/actix/actix-extras/pull/246
## 0.4.0
## 0.4.0 - 2022-03-01
- Update `actix-web` dependency to `4`.
## 0.4.0-beta.9
## 0.4.0-beta.9 - 2022-02-07
- Relax body type bounds on middleware impl. [#223]
- Update `actix-web` dependency to `4.0.0-rc.1`.
[#223]: https://github.com/actix/actix-extras/pull/223
## 0.4.0-beta.8
## 0.4.0-beta.8 - 2022-01-21
- No significant changes since `0.4.0-beta.7`.
## 0.4.0-beta.7
## 0.4.0-beta.7 - 2021-12-29
- Update `actix-web` dependency to `4.0.0.beta-18`. [#218]
- Minimum supported Rust version (MSRV) is now 1.54.
[#218]: https://github.com/actix/actix-extras/pull/218
## 0.4.0-beta.6
## 0.4.0-beta.6 - 2021-12-18
- Update `actix-web` dependency to `4.0.0.beta-15`. [#216]
[#216]: https://github.com/actix/actix-extras/pull/216
## 0.4.0-beta.5
## 0.4.0-beta.5 - 2021-12-12
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.4.0-beta.4
## 0.4.0-beta.4 - 2021-11-22
- No significant changes since `0.4.0-beta.3`.
## 0.4.0-beta.3
## 0.4.0-beta.3 - 2021-10-21
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.4.0-beta.2
## 0.4.0-beta.2 - 2021-06-27
- No notable changes.
## 0.4.0-beta.1
## 0.4.0-beta.1 - 2021-04-02
- Rename `CookieIdentityPolicy::{max_age => max_age_secs}`. [#168]
- Rename `CookieIdentityPolicy::{max_age_time => max_age}`. [#168]
- Update `actix-web` dependency to 4.0.0 beta.
@ -61,31 +128,31 @@
[#168]: https://github.com/actix/actix-extras/pull/168
## 0.3.1
## 0.3.1 - 2020-09-20
- Add method to set `HttpOnly` flag on cookie identity. [#102]
[#102]: https://github.com/actix/actix-extras/pull/102
## 0.3.0
## 0.3.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimum supported Rust version (MSRV) is now 1.42.0.
## 0.3.0-alpha.1
## 0.3.0-alpha.1 - 2020-03-14
- Update the `time` dependency to 0.2.7
- Update the `actix-web` dependency to 3.0.0-alpha.1
- Minimize `futures` dependency
## 0.2.1
## 0.2.1 - 2020-01-10
- Fix panic with already borrowed: BorrowMutError #1263
## 0.2.0 - 2019-12-20
- Use actix-web 2.0
## 0.1.0 - 2019-06-xx
- Move identity middleware to separate crate

View File

@ -1,38 +1,41 @@
[package]
name = "actix-identity"
version = "0.4.0"
version = "0.8.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>",
]
description = "Identity service for Actix Web"
description = "Identity management for Actix Web"
keywords = ["actix", "auth", "identity", "web", "security"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
license = "MIT OR Apache-2.0"
edition = "2018"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
name = "actix_identity"
path = "src/lib.rs"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[dependencies]
actix-service = "2"
actix-session = "0.7"
actix-session = "0.10"
actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
anyhow = "1"
env_logger = "0.9"
futures-core = "0.3.7"
derive_more = { version = "2", features = ["display", "error", "from"] }
futures-core = "0.3.17"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
time = "0.3"
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
[dev-dependencies]
actix-http = "3"
actix-web = { version = "4", default_features = false, features = ["macros", "cookies", "secure-cookies"] }
actix-session = { version = "0.7", features = ["redis-rs-session", "cookie-session"] }
actix-web = { version = "4", default-features = false, features = ["macros", "cookies", "secure-cookies"] }
actix-session = { version = "0.10", features = ["redis-session", "cookie-session"] }
env_logger = "0.11"
reqwest = { version = "0.12", default-features = false, features = ["cookies", "json"] }
uuid = { version = "1", features = ["v4"] }
reqwest = { version = "0.11", default_features = false, features = ["cookies", "json"] }
[lints]
workspace = true

View File

@ -1,13 +1,106 @@
# actix-identity
> Identity service for actix-web framework.
> Identity management for Actix Web.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-identity?label=latest)](https://crates.io/crates/actix-identity)
[![Documentation](https://docs.rs/actix-identity/badge.svg?version=0.4.0)](https://docs.rs/actix-identity/0.4.0)
[![Documentation](https://docs.rs/actix-identity/badge.svg?version=0.8.0)](https://docs.rs/actix-identity/0.8.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-identity)
[![Dependency Status](https://deps.rs/crate/actix-identity/0.4.0/status.svg)](https://deps.rs/crate/actix-identity/0.4.0)
[![Dependency Status](https://deps.rs/crate/actix-identity/0.8.0/status.svg)](https://deps.rs/crate/actix-identity/0.8.0)
## Documentation & community resources
<!-- prettier-ignore-end -->
* [API Documentation](https://docs.rs/actix-identity)
* Minimum Supported Rust Version (MSRV): 1.57
<!-- cargo-rdme start -->
Identity management for Actix Web.
`actix-identity` can be used to track identity of a user across multiple requests. It is built on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session).
## Getting started
To start using identity management in your Actix Web application you must register [`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`:
```rust
use actix_web::{cookie::Key, App, HttpServer, HttpResponse};
use actix_identity::IdentityMiddleware;
use actix_session::{storage::RedisSessionStore, SessionMiddleware};
#[actix_web::main]
async fn main() {
// When using `Key::generate()` it is important to initialize outside of the
// `HttpServer::new` closure. When deployed the secret key should be read from a
// configuration file or environment variables.
let secret_key = Key::generate();
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap();
HttpServer::new(move || {
App::new()
// Install the identity framework first.
.wrap(IdentityMiddleware::default())
// 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
// order of registration when it receives an incoming request.
.wrap(SessionMiddleware::new(
redis_store.clone(),
secret_key.clone(),
))
// Your request handlers [...]
})
}
```
User identities can be created, accessed and destroyed using the [`Identity`] extractor in your request handlers:
```rust
use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage};
use actix_identity::Identity;
use actix_session::storage::RedisSessionStore;
#[get("/")]
async fn index(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
format!("Welcome! {}", user.id().unwrap())
} else {
"Welcome Anonymous!".to_owned()
}
}
#[post("/login")]
async fn login(request: HttpRequest) -> impl Responder {
// Some kind of authentication should happen here
// e.g. password-based, biometric, etc.
// [...]
// attach a verified user identity to the active session
Identity::login(&request.extensions(), "User1".into()).unwrap();
HttpResponse::Ok()
}
#[post("/logout")]
async fn logout(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
user.logout();
}
HttpResponse::Ok()
}
```
## Advanced configuration
By default, `actix-identity` does not automatically log out users. You can change this behaviour by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`].
In particular, you can automatically log out users who:
- have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`]);
- logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]).
[`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline
[`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline
<!-- cargo-rdme end -->

View File

@ -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

View File

@ -9,6 +9,9 @@ pub(crate) struct Configuration {
pub(crate) on_logout: LogoutBehaviour,
pub(crate) login_deadline: Option<Duration>,
pub(crate) visit_deadline: Option<Duration>,
pub(crate) id_key: &'static str,
pub(crate) last_visit_unix_timestamp_key: &'static str,
pub(crate) login_unix_timestamp_key: &'static str,
}
impl Default for Configuration {
@ -17,6 +20,9 @@ impl Default for Configuration {
on_logout: LogoutBehaviour::PurgeSession,
login_deadline: None,
visit_deadline: None,
id_key: "actix_identity.user_id",
last_visit_unix_timestamp_key: "actix_identity.last_visited_at",
login_unix_timestamp_key: "actix_identity.logged_in_at",
}
}
}
@ -58,6 +64,24 @@ impl IdentityMiddlewareBuilder {
}
}
/// Set a custom key to identify the user in the session.
pub fn id_key(mut self, key: &'static str) -> Self {
self.configuration.id_key = key;
self
}
/// Set a custom key to store the last visited unix timestamp.
pub fn last_visit_unix_timestamp_key(mut self, key: &'static str) -> Self {
self.configuration.last_visit_unix_timestamp_key = key;
self
}
/// Set a custom key to store the login unix timestamp.
pub fn login_unix_timestamp_key(mut self, key: &'static str) -> Self {
self.configuration.login_unix_timestamp_key = key;
self
}
/// Determines how [`Identity::logout`](crate::Identity::logout) affects the current session.
///
/// By default, the current session is purged ([`LogoutBehaviour::PurgeSession`]).

View File

@ -0,0 +1,70 @@
//! Failure modes of identity operations.
use actix_session::{SessionGetError, SessionInsertError};
use actix_web::{cookie::time::error::ComponentRange, http::StatusCode, ResponseError};
use derive_more::derive::{Display, Error, From};
/// Error that can occur during login attempts.
#[derive(Debug, Display, Error, From)]
#[display("{_0}")]
pub struct LoginError(SessionInsertError);
impl ResponseError for LoginError {
fn status_code(&self) -> StatusCode {
StatusCode::UNAUTHORIZED
}
}
/// Error encountered when working with a session that has expired.
#[derive(Debug, Display, Error)]
#[display("The given session has expired and is no longer valid")]
pub struct SessionExpiryError(#[error(not(source))] pub(crate) ComponentRange);
/// The identity information has been lost.
///
/// Seeing this error in user code indicates a bug in actix-identity.
#[derive(Debug, Display, Error)]
#[display(
"The identity information in the current session has disappeared after having been \
successfully validated. This is likely to be a bug."
)]
#[non_exhaustive]
pub struct LostIdentityError;
/// There is no identity information attached to the current session.
#[derive(Debug, Display, Error)]
#[display("There is no identity information attached to the current session")]
#[non_exhaustive]
pub struct MissingIdentityError;
/// Errors that can occur while retrieving an identity.
#[derive(Debug, Display, Error, From)]
#[non_exhaustive]
pub enum GetIdentityError {
/// The session has expired.
#[display("{_0}")]
SessionExpiryError(SessionExpiryError),
/// No identity is found in a session.
#[display("{_0}")]
MissingIdentityError(MissingIdentityError),
/// Failed to accessing the session store.
#[display("{_0}")]
SessionGetError(SessionGetError),
/// Identity info was lost after being validated.
///
/// Seeing this error indicates a bug in actix-identity.
#[display("{_0}")]
LostIdentityError(LostIdentityError),
}
impl ResponseError for GetIdentityError {
fn status_code(&self) -> StatusCode {
match self {
Self::LostIdentityError(_) => StatusCode::INTERNAL_SERVER_ERROR,
_ => StatusCode::UNAUTHORIZED,
}
}
}

View File

@ -6,9 +6,13 @@ use actix_web::{
http::StatusCode,
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse,
};
use anyhow::{anyhow, Context};
use crate::config::LogoutBehaviour;
use crate::{
config::LogoutBehaviour,
error::{
GetIdentityError, LoginError, LostIdentityError, MissingIdentityError, SessionExpiryError,
},
};
/// A verified user identity. It can be used as a request extractor.
///
@ -78,6 +82,9 @@ pub(crate) struct IdentityInner {
pub(crate) logout_behaviour: LogoutBehaviour,
pub(crate) is_login_deadline_enabled: bool,
pub(crate) is_visit_deadline_enabled: bool,
pub(crate) id_key: &'static str,
pub(crate) last_visit_unix_timestamp_key: &'static str,
pub(crate) login_unix_timestamp_key: &'static str,
}
impl IdentityInner {
@ -95,20 +102,13 @@ impl IdentityInner {
}
/// Retrieve the user id attached to the current session.
fn get_identity(&self) -> Result<String, anyhow::Error> {
fn get_identity(&self) -> Result<String, GetIdentityError> {
self.session
.get::<String>(ID_KEY)
.context("Failed to deserialize the user identifier attached to the current session")?
.ok_or_else(|| {
anyhow!("There is no identity information attached to the current session")
})
.get::<String>(self.id_key)?
.ok_or_else(|| MissingIdentityError.into())
}
}
pub(crate) const ID_KEY: &str = "actix_identity.user_id";
pub(crate) const LAST_VISIT_UNIX_TIMESTAMP_KEY: &str = "actix_identity.last_visited_at";
pub(crate) const LOGIN_UNIX_TIMESTAMP_KEY: &str = "actix_identity.logged_in_at";
impl Identity {
/// Return the user id associated to the current session.
///
@ -126,10 +126,11 @@ impl Identity {
/// }
/// }
/// ```
pub fn id(&self) -> Result<String, anyhow::Error> {
self.0.session.get(ID_KEY)?.ok_or_else(|| {
anyhow!("Bug: the identity information attached to the current session has disappeared")
})
pub fn id(&self) -> Result<String, GetIdentityError> {
self.0
.session
.get(self.0.id_key)?
.ok_or_else(|| LostIdentityError.into())
}
/// Attach a valid user identity to the current session.
@ -149,13 +150,18 @@ impl Identity {
/// HttpResponse::Ok()
/// }
/// ```
pub fn login(ext: &Extensions, id: String) -> Result<Self, anyhow::Error> {
pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> {
let inner = IdentityInner::extract(ext);
inner.session.insert(ID_KEY, id)?;
inner.session.insert(
LOGIN_UNIX_TIMESTAMP_KEY,
OffsetDateTime::now_utc().unix_timestamp(),
)?;
inner.session.insert(inner.id_key, id)?;
let now = OffsetDateTime::now_utc().unix_timestamp();
if inner.is_login_deadline_enabled {
inner.session.insert(inner.login_unix_timestamp_key, now)?;
}
if inner.is_visit_deadline_enabled {
inner
.session
.insert(inner.last_visit_unix_timestamp_key, now)?;
}
inner.session.renew();
Ok(Self(inner))
}
@ -186,39 +192,49 @@ impl Identity {
self.0.session.purge();
}
LogoutBehaviour::DeleteIdentityKeys => {
self.0.session.remove(ID_KEY);
self.0.session.remove(self.0.id_key);
if self.0.is_login_deadline_enabled {
self.0.session.remove(LOGIN_UNIX_TIMESTAMP_KEY);
self.0.session.remove(self.0.login_unix_timestamp_key);
}
if self.0.is_visit_deadline_enabled {
self.0.session.remove(LAST_VISIT_UNIX_TIMESTAMP_KEY);
self.0.session.remove(self.0.last_visit_unix_timestamp_key);
}
}
}
}
pub(crate) fn extract(ext: &Extensions) -> Result<Self, anyhow::Error> {
pub(crate) fn extract(ext: &Extensions) -> Result<Self, GetIdentityError> {
let inner = IdentityInner::extract(ext);
inner.get_identity()?;
Ok(Self(inner))
}
pub(crate) fn logged_at(&self) -> Result<Option<OffsetDateTime>, anyhow::Error> {
self.0
pub(crate) fn logged_at(&self) -> Result<Option<OffsetDateTime>, GetIdentityError> {
Ok(self
.0
.session
.get(LOGIN_UNIX_TIMESTAMP_KEY)?
.get(self.0.login_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(anyhow::Error::from)
.map_err(SessionExpiryError)?)
}
pub(crate) fn last_visited_at(&self) -> Result<Option<OffsetDateTime>, anyhow::Error> {
self.0
pub(crate) fn last_visited_at(&self) -> Result<Option<OffsetDateTime>, GetIdentityError> {
Ok(self
.0
.session
.get(LAST_VISIT_UNIX_TIMESTAMP_KEY)?
.get(self.0.last_visit_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(anyhow::Error::from)
.map_err(SessionExpiryError)?)
}
pub(crate) fn set_last_visited_at(&self) -> Result<(), LoginError> {
let now = OffsetDateTime::now_utc().unix_timestamp();
self.0
.session
.insert(self.0.last_visit_unix_timestamp_key, now)?;
Ok(())
}
}

View File

@ -1,27 +1,27 @@
use actix_web::{dev::ServiceRequest, guard::GuardContext, HttpMessage, HttpRequest};
use crate::Identity;
use crate::{error::GetIdentityError, Identity};
/// Helper trait to retrieve an [`Identity`] instance from various `actix-web`'s types.
pub trait IdentityExt {
/// Retrieve the identity attached to the current session, if available.
fn get_identity(&self) -> Result<Identity, anyhow::Error>;
fn get_identity(&self) -> Result<Identity, GetIdentityError>;
}
impl IdentityExt for HttpRequest {
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
Identity::extract(&self.extensions())
}
}
impl IdentityExt for ServiceRequest {
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
Identity::extract(&self.extensions())
}
}
impl<'a> IdentityExt for GuardContext<'a> {
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
impl IdentityExt for GuardContext<'_> {
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
Identity::extract(&self.req_data())
}
}

View File

@ -1,100 +1,109 @@
//! Identity management for Actix Web.
//!
//! `actix-identity` can be used to track identity of a user across multiple requests. It is built
//! on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session).
//!
//! # Getting started
//! To start using identity management in your Actix Web application you must register
//! [`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`:
//!
//! ```no_run
//! # use actix_web::web;
//! use actix_web::{cookie::Key, App, HttpServer, HttpResponse};
//! use actix_identity::IdentityMiddleware;
//! use actix_session::{storage::RedisSessionStore, SessionMiddleware};
//!
//! #[actix_web::main]
//! async fn main() {
//! let secret_key = Key::generate();
//! let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
//! .await
//! .unwrap();
//!
//! HttpServer::new(move || {
//! App::new()
//! // Install the identity framework first.
//! .wrap(IdentityMiddleware::default())
//! // 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
//! // order of registration when it receives an incoming request.
//! .wrap(SessionMiddleware::new(
//! redis_store.clone(),
//! secret_key.clone()
//! ))
//! // Your request handlers [...]
//! # .default_service(web::to(|| HttpResponse::Ok()))
//! })
//! # ;
//! }
//! ```
//!
//! User identities can be created, accessed and destroyed using the [`Identity`] extractor in your
//! request handlers:
//!
//! ```no_run
//! use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage};
//! use actix_identity::Identity;
//! use actix_session::storage::RedisSessionStore;
//!
//! #[get("/")]
//! async fn index(user: Option<Identity>) -> impl Responder {
//! if let Some(user) = user {
//! format!("Welcome! {}", user.id().unwrap())
//! } else {
//! "Welcome Anonymous!".to_owned()
//! }
//! }
//!
//! #[post("/login")]
//! async fn login(request: HttpRequest) -> impl Responder {
//! // Some kind of authentication should happen here
//! // e.g. password-based, biometric, etc.
//! // [...]
//!
//! // attach a verified user identity to the active session
//! Identity::login(&request.extensions(), "User1".into()).unwrap();
//!
//! HttpResponse::Ok()
//! }
//!
//! #[post("/logout")]
//! async fn logout(user: Identity) -> impl Responder {
//! user.logout();
//! HttpResponse::Ok()
//! }
//! ```
//!
//! # Advanced configuration
//! By default, `actix-identity` does not automatically log out users. You can change this behaviour
//! by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`].
//!
//! In particular, you can automatically log out users who:
//! - have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`];
//! - logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]).
//!
//! [`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline
//! [`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline
/*!
Identity management for Actix Web.
`actix-identity` can be used to track identity of a user across multiple requests. It is built
on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session).
# Getting started
To start using identity management in your Actix Web application you must register
[`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`:
```no_run
# use actix_web::web;
use actix_web::{cookie::Key, App, HttpServer, HttpResponse};
use actix_identity::IdentityMiddleware;
use actix_session::{storage::RedisSessionStore, SessionMiddleware};
#[actix_web::main]
async fn main() {
// When using `Key::generate()` it is important to initialize outside of the
// `HttpServer::new` closure. When deployed the secret key should be read from a
// configuration file or environment variables.
let secret_key = Key::generate();
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap();
HttpServer::new(move || {
App::new()
// Install the identity framework first.
.wrap(IdentityMiddleware::default())
// 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
// order of registration when it receives an incoming request.
.wrap(SessionMiddleware::new(
redis_store.clone(),
secret_key.clone(),
))
// Your request handlers [...]
# .default_service(web::to(|| HttpResponse::Ok()))
})
# ;
}
```
User identities can be created, accessed and destroyed using the [`Identity`] extractor in your
request handlers:
```no_run
use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage};
use actix_identity::Identity;
use actix_session::storage::RedisSessionStore;
#[get("/")]
async fn index(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
format!("Welcome! {}", user.id().unwrap())
} else {
"Welcome Anonymous!".to_owned()
}
}
#[post("/login")]
async fn login(request: HttpRequest) -> impl Responder {
// Some kind of authentication should happen here
// e.g. password-based, biometric, etc.
// [...]
// attach a verified user identity to the active session
Identity::login(&request.extensions(), "User1".into()).unwrap();
HttpResponse::Ok()
}
#[post("/logout")]
async fn logout(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
user.logout();
}
HttpResponse::Ok()
}
```
# Advanced configuration
By default, `actix-identity` does not automatically log out users. You can change this behaviour
by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`].
In particular, you can automatically log out users who:
- have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`]);
- logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]).
[`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline
[`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline
*/
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style, missing_docs)]
#![warn(future_incompatible)]
#![deny(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod config;
pub mod error;
mod identity;
mod identity_ext;
mod middleware;
pub use self::identity::Identity;
pub use self::identity_ext::IdentityExt;
pub use self::middleware::IdentityMiddleware;
pub use self::{identity::Identity, identity_ext::IdentityExt, middleware::IdentityMiddleware};

View File

@ -4,12 +4,11 @@ use actix_session::SessionExt;
use actix_utils::future::{ready, Ready};
use actix_web::{
body::MessageBody,
cookie::time::format_description::well_known::Rfc3339,
cookie::time::{format_description::well_known::Rfc3339, OffsetDateTime},
dev::{Service, ServiceRequest, ServiceResponse, Transform},
Error, HttpMessage as _, Result,
};
use futures_core::future::LocalBoxFuture;
use time::OffsetDateTime;
use crate::{
config::{Configuration, IdentityMiddlewareBuilder},
@ -115,6 +114,9 @@ where
logout_behaviour: configuration.on_logout.clone(),
is_login_deadline_enabled: configuration.login_deadline.is_some(),
is_visit_deadline_enabled: configuration.visit_deadline.is_some(),
id_key: configuration.id_key,
last_visit_unix_timestamp_key: configuration.last_visit_unix_timestamp_key,
login_unix_timestamp_key: configuration.login_unix_timestamp_key,
};
req.extensions_mut().insert(identity_inner);
enforce_policies(&req, &configuration);
@ -163,6 +165,12 @@ fn enforce_policies(req: &ServiceRequest, configuration: &Configuration) {
) {
identity.logout();
return;
} else if let Err(err) = identity.set_last_visited_at() {
tracing::warn!(
error.display = %err,
error.debug = ?err,
"Failed to set the last visited timestamp on `Identity` for an incoming request."
);
}
}
}

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use actix_identity::{config::LogoutBehaviour, IdentityMiddleware};
use actix_web::http::StatusCode;
use reqwest::StatusCode;
use crate::{fixtures::user_id, test_app::TestApp};
@ -28,6 +28,33 @@ async fn login_works() {
assert!(response.status().is_success());
}
#[actix_web::test]
async fn custom_keys_work_as_expected() {
let custom_id_key = "custom.user_id";
let custom_last_visited_key = "custom.last_visited_at";
let custom_logged_in_key = "custom.logged_in_at";
let app = TestApp::spawn_with_config(
IdentityMiddleware::builder()
.id_key(custom_id_key)
.last_visit_unix_timestamp_key(custom_last_visited_key)
.login_unix_timestamp_key(custom_logged_in_key),
);
let user_id = user_id();
let body = app.post_login(user_id.clone()).await;
assert_eq!(body.user_id, Some(user_id.clone()));
let response = app.get_identity_required().await;
assert!(response.status().is_success());
let response = app.post_logout().await;
assert!(response.status().is_success());
let response = app.get_identity_required().await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[actix_web::test]
async fn logging_in_again_replaces_the_current_identity() {
let app = TestApp::spawn();
@ -147,6 +174,23 @@ async fn login_deadline_does_not_log_users_out_before_their_time() {
assert_eq!(body.user_id, Some(user_id));
}
#[actix_web::test]
async fn visit_deadline_does_not_log_users_out_before_their_time() {
// 1 hour
let visit_deadline = Duration::from_secs(60 * 60);
let app = TestApp::spawn_with_config(
IdentityMiddleware::builder().visit_deadline(Some(visit_deadline)),
);
let user_id = user_id();
// Log-in
let body = app.post_login(user_id.clone()).await;
assert_eq!(body.user_id, Some(user_id.clone()));
let body = app.get_current().await;
assert_eq!(body.user_id, Some(user_id));
}
#[actix_web::test]
async fn user_is_logged_out_when_visit_deadline_is_elapsed() {
let visit_deadline = Duration::from_millis(10);

View File

@ -32,7 +32,8 @@ impl TestApp {
.listen(listener)
.unwrap()
.run();
let _ = actix_web::rt::spawn(server);
actix_web::rt::spawn(server);
let client = reqwest::Client::builder()
.cookie_store(true)
@ -103,14 +104,14 @@ impl TestApp {
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EndpointResponse {
pub user_id: Option<String>,
pub counter: i32,
pub session_status: String,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct LoginRequest {
user_id: String,
}
@ -135,7 +136,7 @@ async fn increment(session: Session, user: Option<Identity>) -> HttpResponse {
.get::<i32>("counter")
.unwrap_or(Some(0))
.map_or(1, |inner| inner + 1);
session.insert("counter", &counter).unwrap();
session.insert("counter", counter).unwrap();
HttpResponse::Ok().json(&EndpointResponse {
user_id,

View File

@ -1,24 +1,41 @@
# Changes
## Unreleased - 2022-xx-xx
## Unreleased
- Update `redis` dependency to `0.29`.
- Update `actix-session` dependency to `0.9`.
## 0.5.1
- No significant changes since `0.5.0`.
## 0.5.0
- Update `redis` dependency to `0.23`.
- Update `actix-session` dependency to `0.8`.
## 0.4.0
- Add `Builder::key_by` for setting a custom rate limit key function.
- Implement `Default` for `RateLimiter`.
- `RateLimiter` is marked `#[non_exhaustive]`; use `RateLimiter::default()` instead.
- In the middleware errors from the count function are matched and respond with `INTERNAL_SERVER_ERROR` if it's an unexpected error, instead of the default `TOO_MANY_REQUESTS`.
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
## 0.3.0
## 0.3.0 - 2022-07-11
- `Limiter::builder` now takes an `impl Into<String>`.
- Removed lifetime from `Builder`.
- Updated `actix-session` dependency to `0.7`.
## 0.2.0
## 0.2.0 - 2022-03-22
- Update Actix Web dependency to v4 ecosystem. [#229]
- Update Tokio dependencies to v1 ecosystem. [#229]
- Rename `Limiter::{build => builder}()`. [#232]
- Rename `Builder::{finish => build}()`. [#232]
- Exceeding the rate limit now returns a 429 Too Many Requests response. [#232]
- Update Actix Web dependency to v4 ecosystem.
- Update Tokio dependencies to v1 ecosystem.
- Rename `Limiter::{build => builder}()`.
- Rename `Builder::{finish => build}()`.
- Exceeding the rate limit now returns a 429 Too Many Requests response.
[#229]: https://github.com/actix/actix-extras/pull/229
[#232]: https://github.com/actix/actix-extras/pull/232
## 0.1.4
## 0.1.4 - 2022-03-18
- Adopted into @actix org from <https://github.com/0xmad/actix-limitation>.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-limitation"
version = "0.3.0"
version = "0.5.1"
authors = [
"0xmad <0xmad@users.noreply.github.com>",
"Rob Ede <robjtede@icloud.com>",
@ -8,22 +8,36 @@ authors = [
description = "Rate limiter using a fixed window counter for arbitrary keys, backed by Redis for Actix Web"
keywords = ["actix-web", "rate-api", "rate-limit", "limitation"]
categories = ["asynchronous", "web-programming"]
repository = "https://github.com/actix/actix-extras.git"
license = "MIT OR Apache-2.0"
edition = "2018"
repository = "https://github.com/actix/actix-extras"
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[features]
default = ["session"]
session = ["actix-session"]
[dependencies]
actix-session = "0.7"
actix-utils = "3"
actix-web = { version = "4", default-features = false }
actix-web = { version = "4", default-features = false, features = ["cookies"] }
chrono = "0.4"
derive_more = "0.99.5"
derive_more = { version = "2", features = ["display", "error", "from"] }
log = "0.4"
redis = { version = "0.21", default-features = false, features = ["tokio-comp"] }
redis = { version = "0.29", default-features = false, features = ["tokio-comp"] }
time = "0.3"
# session
actix-session = { version = "0.10", optional = true }
[dev-dependencies]
actix-web = "4"
static_assertions = "1"
uuid = { version = "1", features = ["v4"] }
[lints]
workspace = true

View File

@ -3,23 +3,28 @@
> Rate limiter using a fixed window counter for arbitrary keys, backed by Redis for Actix Web.
> Originally based on <https://github.com/fnichol/limitation>.
<!-- prettier-ignore-start -->
[![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?version=0.3.0)](https://docs.rs/actix-limitation/0.3.0)
[![Documentation](https://docs.rs/actix-limitation/badge.svg?version=0.5.1)](https://docs.rs/actix-limitation/0.5.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-limitation)
[![Dependency Status](https://deps.rs/crate/actix-limitation/0.3.0/status.svg)](https://deps.rs/crate/actix-limitation/0.3.0)
[![Dependency Status](https://deps.rs/crate/actix-limitation/0.5.1/status.svg)](https://deps.rs/crate/actix-limitation/0.5.1)
<!-- prettier-ignore-end -->
## Examples
```toml
[dependencies]
actix-web = "4"
actix-limitation = "0.1.4"
actix-limitation = "0.5"
```
```rust
use std::time::Duration;
use actix_web::{get, web, App, HttpServer, Responder};
use actix_limitation::{Limiter, RateLimiter};
use actix_session::SessionExt as _;
use actix_web::{dev::ServiceRequest, get, web, App, HttpServer, Responder};
use std::{sync::Arc, time::Duration};
#[get("/{id}/{name}")]
async fn index(info: web::Path<(u32, String)>) -> impl Responder {
@ -29,22 +34,24 @@ async fn index(info: web::Path<(u32, String)>) -> impl Responder {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let limiter = web::Data::new(
Limiter::build("redis://127.0.0.1")
.cookie_name("session-id".to_owned())
.session_key("rate-api-id".to_owned())
Limiter::builder("redis://127.0.0.1")
.key_by(|req: &ServiceRequest| {
req.get_session()
.get(&"session-id")
.unwrap_or_else(|_| req.cookie(&"rate-api-id").map(|c| c.to_string()))
})
.limit(5000)
.period(Duration::from_secs(3600)) // 60 minutes
.finish()
.expect("Can't build actix-limiter"),
.build()
.unwrap(),
);
HttpServer::new(move || {
App::new()
.wrap(RateLimiter)
.wrap(RateLimiter::default())
.app_data(limiter.clone())
.service(index)
})
.bind("127.0.0.1:8080")?
.bind(("127.0.0.1", 8080))?
.run()
.await
}

View File

@ -1,8 +1,11 @@
use std::{borrow::Cow, time::Duration};
use std::{borrow::Cow, sync::Arc, time::Duration};
#[cfg(feature = "session")]
use actix_session::SessionExt as _;
use actix_web::dev::ServiceRequest;
use redis::Client;
use crate::{errors::Error, Limiter};
use crate::{errors::Error, GetArcBoxKeyFn, Limiter};
/// Rate limiter builder.
#[derive(Debug)]
@ -10,7 +13,9 @@ pub struct Builder {
pub(crate) redis_url: String,
pub(crate) limit: usize,
pub(crate) period: Duration,
pub(crate) get_key_fn: Option<GetArcBoxKeyFn>,
pub(crate) cookie_name: Cow<'static, str>,
#[cfg(feature = "session")]
pub(crate) session_key: Cow<'static, str>,
}
@ -27,14 +32,38 @@ impl Builder {
self
}
/// Set name of cookie to be sent.
/// Sets rate limit key derivation function.
///
/// Should not be used in combination with `cookie_name` or `session_key` as they conflict.
pub fn key_by<F>(&mut self, resolver: F) -> &mut Self
where
F: Fn(&ServiceRequest) -> Option<String> + Send + Sync + 'static,
{
self.get_key_fn = Some(Arc::new(resolver));
self
}
/// Sets name of cookie to be sent.
///
/// This method should not be used in combination of `key_by` as they conflict.
#[deprecated = "Prefer `key_by`."]
pub fn cookie_name(&mut self, cookie_name: impl Into<Cow<'static, str>>) -> &mut Self {
if self.get_key_fn.is_some() {
panic!("This method should not be used in combination of get_key as they overwrite each other")
}
self.cookie_name = cookie_name.into();
self
}
/// Set session key to be used in backend.
/// Sets session key to be used in backend.
///
/// This method should not be used in combination of `key_by` as they conflict.
#[deprecated = "Prefer `key_by`."]
#[cfg(feature = "session")]
pub fn session_key(&mut self, session_key: impl Into<Cow<'static, str>>) -> &mut Self {
if self.get_key_fn.is_some() {
panic!("This method should not be used in combination of get_key as they overwrite each other")
}
self.session_key = session_key.into();
self
}
@ -43,13 +72,35 @@ impl Builder {
///
/// Note that this method will connect to the Redis server to test its connection which is a
/// **synchronous** operation.
pub fn build(&self) -> Result<Limiter, Error> {
pub fn build(&mut self) -> Result<Limiter, Error> {
let get_key = if let Some(resolver) = self.get_key_fn.clone() {
resolver
} else {
let cookie_name = self.cookie_name.clone();
#[cfg(feature = "session")]
let session_key = self.session_key.clone();
let closure: GetArcBoxKeyFn = Arc::new(Box::new(move |req: &ServiceRequest| {
#[cfg(feature = "session")]
let res = req
.get_session()
.get(&session_key)
.unwrap_or_else(|_| req.cookie(&cookie_name).map(|c| c.to_string()));
#[cfg(not(feature = "session"))]
let res = req.cookie(&cookie_name).map(|c| c.to_string());
res
}));
closure
};
Ok(Limiter {
client: Client::open(self.redis_url.as_str())?,
limit: self.limit,
period: self.period,
cookie_name: self.cookie_name.clone(),
session_key: self.session_key.clone(),
get_key_fn: get_key,
})
}
}
@ -66,13 +117,16 @@ mod tests {
redis_url: redis_url.to_owned(),
limit: 100,
period,
get_key_fn: Some(Arc::new(|_| None)),
cookie_name: Cow::Owned("session".to_string()),
#[cfg(feature = "session")]
session_key: Cow::Owned("rate-api".to_string()),
};
assert_eq!(builder.redis_url, redis_url);
assert_eq!(builder.limit, 100);
assert_eq!(builder.period, period);
#[cfg(feature = "session")]
assert_eq!(builder.session_key, "rate-api");
assert_eq!(builder.cookie_name, "session");
}
@ -85,22 +139,16 @@ mod tests {
redis_url: redis_url.to_owned(),
limit: 100,
period: Duration::from_secs(10),
session_key: Cow::Borrowed("key"),
get_key_fn: Some(Arc::new(|_| None)),
cookie_name: Cow::Borrowed("sid"),
#[cfg(feature = "session")]
session_key: Cow::Borrowed("key"),
};
let limiter = builder
.limit(200)
.period(period)
.cookie_name("session".to_string())
.session_key("rate-api".to_string())
.build()
.unwrap();
let limiter = builder.limit(200).period(period).build().unwrap();
assert_eq!(limiter.limit, 200);
assert_eq!(limiter.period, period);
assert_eq!(limiter.session_key, "rate-api");
assert_eq!(limiter.cookie_name, "session");
}
#[test]
@ -112,8 +160,10 @@ mod tests {
redis_url: redis_url.to_owned(),
limit: 100,
period: Duration::from_secs(10),
session_key: Cow::Borrowed("key"),
get_key_fn: Some(Arc::new(|_| None)),
cookie_name: Cow::Borrowed("sid"),
#[cfg(feature = "session")]
session_key: Cow::Borrowed("key"),
};
builder.limit(200).period(period).build().unwrap();

View File

@ -1,4 +1,4 @@
use derive_more::{Display, Error, From};
use derive_more::derive::{Display, Error, From};
use crate::status::Status;
@ -6,20 +6,20 @@ use crate::status::Status;
#[derive(Debug, Display, Error, From)]
pub enum Error {
/// Redis client failed to connect or run a query.
#[display(fmt = "Redis client failed to connect or run a query")]
#[display("Redis client failed to connect or run a query")]
Client(redis::RedisError),
/// Limit is exceeded for a key.
#[display(fmt = "Limit is exceeded for a key")]
#[display("Limit is exceeded for a key")]
#[from(ignore)]
LimitExceeded(#[error(not(source))] Status),
/// Time conversion failed.
#[display(fmt = "Time conversion failed")]
#[display("Time conversion failed")]
Time(time::error::ComponentRange),
/// Generic error.
#[display(fmt = "Generic error")]
#[display("Generic error")]
#[from(ignore)]
Other(#[error(not(source))] String),
}

View File

@ -3,12 +3,13 @@
//! ```toml
//! [dependencies]
//! actix-web = "4"
//! actix-limitation = "0.1.4"
#![doc = concat!("actix-limitation = \"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"),"\"")]
//! ```
//!
//! ```no_run
//! use std::time::Duration;
//! use actix_web::{get, web, App, HttpServer, Responder};
//! use std::{sync::Arc, time::Duration};
//! use actix_web::{dev::ServiceRequest, get, web, App, HttpServer, Responder};
//! use actix_session::SessionExt as _;
//! use actix_limitation::{Limiter, RateLimiter};
//!
//! #[get("/{id}/{name}")]
@ -20,8 +21,11 @@
//! async fn main() -> std::io::Result<()> {
//! let limiter = web::Data::new(
//! Limiter::builder("redis://127.0.0.1")
//! .cookie_name("session-id".to_owned())
//! .session_key("rate-api-id".to_owned())
//! .key_by(|req: &ServiceRequest| {
//! req.get_session()
//! .get(&"session-id")
//! .unwrap_or_else(|_| req.cookie(&"rate-api-id").map(|c| c.to_string()))
//! })
//! .limit(5000)
//! .period(Duration::from_secs(3600)) // 60 minutes
//! .build()
@ -30,24 +34,25 @@
//!
//! HttpServer::new(move || {
//! App::new()
//! .wrap(RateLimiter)
//! .wrap(RateLimiter::default())
//! .app_data(limiter.clone())
//! .service(index)
//! })
//! .bind("127.0.0.1:8080")?
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! }
//! ```
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![warn(missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{borrow::Cow, time::Duration};
use std::{borrow::Cow, fmt, sync::Arc, time::Duration};
use actix_web::dev::ServiceRequest;
use redis::Client;
mod builder;
@ -55,10 +60,7 @@ mod errors;
mod middleware;
mod status;
pub use self::builder::Builder;
pub use self::errors::Error;
pub use self::middleware::RateLimiter;
pub use self::status::Status;
pub use self::{builder::Builder, errors::Error, middleware::RateLimiter, status::Status};
/// Default request limit.
pub const DEFAULT_REQUEST_LIMIT: usize = 5000;
@ -70,16 +72,34 @@ pub const DEFAULT_PERIOD_SECS: u64 = 3600;
pub const DEFAULT_COOKIE_NAME: &str = "sid";
/// Default session key.
#[cfg(feature = "session")]
pub const DEFAULT_SESSION_KEY: &str = "rate-api-id";
/// Helper trait to impl Debug on GetKeyFn type
trait GetKeyFnT: Fn(&ServiceRequest) -> Option<String> {}
impl<T> GetKeyFnT for T where T: Fn(&ServiceRequest) -> Option<String> {}
/// Get key function type with auto traits
type GetKeyFn = dyn GetKeyFnT + Send + Sync;
/// Get key resolver function type
impl fmt::Debug for GetKeyFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GetKeyFn")
}
}
/// Wrapped Get key function Trait
type GetArcBoxKeyFn = Arc<GetKeyFn>;
/// Rate limiter.
#[derive(Debug, Clone)]
pub struct Limiter {
client: Client,
limit: usize,
period: Duration,
cookie_name: Cow<'static, str>,
session_key: Cow<'static, str>,
get_key_fn: GetArcBoxKeyFn,
}
impl Limiter {
@ -93,7 +113,9 @@ impl Limiter {
redis_url: redis_url.into(),
limit: DEFAULT_REQUEST_LIMIT,
period: Duration::from_secs(DEFAULT_PERIOD_SECS),
get_key_fn: None,
cookie_name: Cow::Borrowed(DEFAULT_COOKIE_NAME),
#[cfg(feature = "session")]
session_key: Cow::Borrowed(DEFAULT_SESSION_KEY),
}
}
@ -115,7 +137,7 @@ impl Limiter {
let key = key.into();
let expires = self.period.as_secs();
let mut connection = self.client.get_tokio_connection().await?;
let mut connection = self.client.get_multiplexed_tokio_connection().await?;
// The seed of this approach is outlined Atul R in a blog post about rate limiting using
// NodeJS and Redis. For more details, see https://blog.atulr.com/rate-limiter
@ -146,14 +168,12 @@ mod tests {
#[test]
fn test_create_limiter() {
let builder = Limiter::builder("redis://127.0.0.1:6379/1");
let mut builder = Limiter::builder("redis://127.0.0.1:6379/1");
let limiter = builder.build();
assert!(limiter.is_ok());
let limiter = limiter.unwrap();
assert_eq!(limiter.limit, 5000);
assert_eq!(limiter.period, Duration::from_secs(3600));
assert_eq!(limiter.cookie_name, DEFAULT_COOKIE_NAME);
assert_eq!(limiter.session_key, DEFAULT_SESSION_KEY);
}
}

View File

@ -1,19 +1,18 @@
use std::{future::Future, pin::Pin, rc::Rc};
use actix_session::SessionExt as _;
use actix_utils::future::{ok, Ready};
use actix_web::{
body::EitherBody,
cookie::Cookie,
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
http::{header::COOKIE, StatusCode},
http::StatusCode,
web, Error, HttpResponse,
};
use crate::Limiter;
use crate::{Error as LimitationError, Limiter};
/// Rate limit middleware.
#[derive(Debug)]
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct RateLimiter;
impl<S, B> Transform<S, ServiceRequest> for RateLimiter
@ -54,41 +53,57 @@ where
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
// A mis-configuration of the Actix App will result in a **runtime** failure, so the expect
// A misconfiguration of the Actix App will result in a **runtime** failure, so the expect
// method description is important context for the developer.
let limiter = req
.app_data::<web::Data<Limiter>>()
.expect("web::Data<Limiter> should be set in app data for RateLimiter middleware")
.clone();
let (key, fallback) = key(&req, limiter.clone());
let key = (limiter.get_key_fn)(&req);
let service = Rc::clone(&self.service);
let key = match key {
Some(key) => key,
None => match fallback {
Some(key) => key,
None => {
return Box::pin(async move {
service
.call(req)
.await
.map(ServiceResponse::map_into_left_body)
});
}
},
None => {
return Box::pin(async move {
service
.call(req)
.await
.map(ServiceResponse::map_into_left_body)
});
}
};
Box::pin(async move {
let status = limiter.count(key.to_string()).await;
if status.is_err() {
log::warn!("Rate limit exceed error for {}", key);
if let Err(err) = status {
match err {
LimitationError::LimitExceeded(_) => {
log::warn!("Rate limit exceed error for {}", key);
Ok(req.into_response(
HttpResponse::new(StatusCode::TOO_MANY_REQUESTS).map_into_right_body(),
))
Ok(req.into_response(
HttpResponse::new(StatusCode::TOO_MANY_REQUESTS).map_into_right_body(),
))
}
LimitationError::Client(e) => {
log::error!("Client request failed, redis error: {}", e);
Ok(req.into_response(
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
.map_into_right_body(),
))
}
_ => {
log::error!("Count failed: {}", err);
Ok(req.into_response(
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
.map_into_right_body(),
))
}
}
} else {
service
.call(req)
@ -98,19 +113,3 @@ where
})
}
}
fn key(req: &ServiceRequest, limiter: web::Data<Limiter>) -> (Option<String>, Option<String>) {
let session = req.get_session();
let result: Option<String> = session.get(&limiter.session_key).unwrap_or(None);
let cookies = req.headers().get_all(COOKIE);
let cookie = cookies
.filter_map(|i| i.to_str().ok())
.find(|i| i.contains(limiter.cookie_name.as_ref()));
let fallback = match cookie {
Some(value) => Cookie::parse(value).ok().map(|i| i.to_string()),
None => None,
};
(result, fallback)
}

View File

@ -1,4 +1,4 @@
use std::{convert::TryInto, ops::Add, time::Duration};
use std::{ops::Add, time::Duration};
use chrono::SubsecRound as _;
@ -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,

View File

@ -1,9 +1,12 @@
use actix_limitation::{Error, Limiter};
use std::time::Duration;
use actix_limitation::{Error, Limiter, RateLimiter};
use actix_web::{dev::ServiceRequest, http::StatusCode, test, web, App, HttpRequest, HttpResponse};
use uuid::Uuid;
#[test]
#[should_panic = "Redis URL did not parse"]
fn test_create_limiter_error() {
async fn test_create_limiter_error() {
Limiter::builder("127.0.0.1").build().unwrap();
}
@ -18,7 +21,7 @@ async fn test_limiter_count() -> Result<(), Error> {
for i in 0..20 {
let status = limiter.count(id.to_string()).await?;
println!("status: {:?}", status);
println!("status: {status:?}");
assert_eq!(20 - status.remaining(), i + 1);
}
@ -51,3 +54,39 @@ async fn test_limiter_count_error() -> Result<(), Error> {
Ok(())
}
#[actix_web::test]
async fn test_limiter_key_by() -> Result<(), Error> {
let cooldown_period = Duration::from_secs(1);
let limiter = Limiter::builder("redis://127.0.0.1:6379/3")
.limit(2)
.period(cooldown_period)
.key_by(|_: &ServiceRequest| Some("fix_key".to_string()))
.build()
.unwrap();
let app = test::init_service(
App::new()
.wrap(RateLimiter::default())
.app_data(web::Data::new(limiter))
.route(
"/",
web::get().to(|_: HttpRequest| async { HttpResponse::Ok().body("ok") }),
),
)
.await;
for _ in 1..2 {
for index in 1..4 {
let req = test::TestRequest::default().to_request();
let resp = test::call_service(&app, req).await;
if index <= 2 {
assert!(resp.status().is_success());
} else {
assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);
}
}
std::thread::sleep(cooldown_period);
}
Ok(())
}

View File

@ -1,33 +1,48 @@
# Changes
## Unreleased - 2022-xx-xx
## Unreleased
## 0.11.0
- Updated `prost` dependency to `0.13`.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.10.0
- Updated `prost` dependency to `0.12`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.9.0
- Added `application/x-protobuf` as an acceptable header.
- Updated `prost` dependency to `0.11`.
## 0.8.0
## 0.8.0 - 2022-06-25
- Update `prost` dependency to `0.10`.
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.7.0
## 0.7.0 - 2022-03-01
- Update `actix-web` dependency to `4`.
## 0.7.0-beta.5
## 0.7.0-beta.5 - 2022-02-03
- Update `prost` dependency to `0.9`.
- Update `actix-web` dependency to `4.0.0-rc.1`.
## 0.7.0-beta.4
## 0.7.0-beta.4 - 2021-12-29
- Minimum supported Rust version (MSRV) is now 1.54.
## 0.7.0-beta.3
## 0.7.0-beta.3 - 2021-12-12
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.7.0-beta.2
## 0.7.0-beta.2 - 2021-10-21
- Bump `prost` version to 0.8. [#197]
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
@ -35,52 +50,52 @@
[#197]: https://github.com/actix/actix-extras/pull/197
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.7.0-beta.1
## 0.7.0-beta.1 - 2021-06-27
- Bump `prost` version to 0.7. [#144]
- Update `actix-web` dependency to 4.0.0 beta.
- Minimum supported Rust version (MSRV) is now 1.46.0.
[#144]: https://github.com/actix/actix-extras/pull/144
## 0.6.0
## 0.6.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimum supported Rust version (MSRV) is now 1.42.0 to use `matches!` macro.
## 0.6.0-alpha.1
## 0.6.0-alpha.1 - 2020-07-06
- Update `actix-web` to 3.0.0-alpha.3
- Minimum supported Rust version(MSRV) is now 1.40.0.
- Minimize `futures` dependency
## 0.5.1 - 2019-02-17
- Move repository to actix-extras
## 0.5.0 - 2019-01-24
- Migrate to actix-web 2.0.0 and std::future
- Update prost to 0.6
- Update bytes to 0.5
## 0.4.1 - 2019-10-03
- Upgrade prost and prost-derive to 0.5.0
## 0.4.0 - 2019-05-18
- Upgrade to actix-web 1.0.0-rc
- Removed `protobuf` method for `HttpRequest` (use `ProtoBuf` extractor instead)
## 0.3.0 - 2019-03-07
- Upgrade to actix-web 0.7.18
## 0.2.0 - 2018-04-10
- Provide protobuf extractor
## 0.1.0 - 2018-03-21
- First release

View File

@ -1,27 +1,31 @@
[package]
name = "actix-protobuf"
version = "0.8.0"
edition = "2018"
version = "0.11.0"
authors = [
"kingxsp <jin.hb.zh@outlook.com>",
"Yuki Okushi <huyuumi.dev@gmail.com>",
]
description = "Protobuf support for Actix Web"
description = "Protobuf payload extractor for Actix Web"
keywords = ["actix", "web", "protobuf", "protocol", "rpc"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
license = "MIT OR Apache-2.0"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
name = "actix_protobuf"
path = "src/lib.rs"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[dependencies]
actix-web = { version = "4", default_features = false }
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false }
prost = { version = "0.10", default_features = false }
actix-web = { version = "4", default-features = false }
derive_more = { version = "2", features = ["display"] }
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
prost = { version = "0.13", default-features = false }
[dev-dependencies]
actix-web = { version = "4", default_features = false, features = ["macros"] }
prost = { version = "0.10", default_features = false, features = ["prost-derive"] }
actix-web = { version = "4", default-features = false, features = ["macros"] }
prost = { version = "0.13", default-features = false, features = ["prost-derive"] }
[lints]
workspace = true

View File

@ -1,11 +1,15 @@
# actix-protobuf
> Protobuf support for Actix Web.
> Protobuf payload extractor for Actix Web.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-protobuf?label=latest)](https://crates.io/crates/actix-protobuf)
[![Documentation](https://docs.rs/actix-protobuf/badge.svg?version=0.8.0)](https://docs.rs/actix-protobuf/0.8.0)
[![Documentation](https://docs.rs/actix-protobuf/badge.svg?version=0.11.0)](https://docs.rs/actix-protobuf/0.11.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-protobuf)
[![Dependency Status](https://deps.rs/crate/actix-protobuf/0.8.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.8.0)
[![Dependency Status](https://deps.rs/crate/actix-protobuf/0.11.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.11.0)
<!-- prettier-ignore-end -->
## Documentation & Resources
@ -23,6 +27,7 @@ use actix_web::*;
pub struct MyObj {
#[prost(int32, tag = "1")]
pub number: i32,
#[prost(string, tag = "2")]
pub name: String,
}
@ -33,7 +38,7 @@ async fn index(msg: ProtoBuf<MyObj>) -> Result<HttpResponse> {
}
```
See [here](https://github.com/actix/actix-extras/tree/master/actix-protobuf/examples/prost-example) for the complete example.
See [here](https://github.com/actix/examples/tree/master/protobuf) for the complete example.
## License

View File

@ -1,6 +1,9 @@
//! Protobuf payload extractor for Actix Web.
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{
fmt,
@ -19,7 +22,7 @@ use actix_web::{
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder, Responder,
ResponseError,
};
use derive_more::Display;
use derive_more::derive::Display;
use futures_util::{
future::{FutureExt as _, LocalBoxFuture},
stream::StreamExt as _,
@ -29,26 +32,28 @@ use prost::{DecodeError as ProtoBufDecodeError, EncodeError as ProtoBufEncodeErr
#[derive(Debug, Display)]
pub enum ProtoBufPayloadError {
/// Payload size is bigger than 256k
#[display(fmt = "Payload size is bigger than 256k")]
#[display("Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[display(fmt = "Content type error")]
#[display("Content type error")]
ContentType,
/// Serialize error
#[display(fmt = "ProtoBuf serialize error: {}", _0)]
#[display("ProtoBuf serialize error: {_0}")]
Serialize(ProtoBufEncodeError),
/// Deserialize error
#[display(fmt = "ProtoBuf deserialize error: {}", _0)]
#[display("ProtoBuf deserialize error: {_0}")]
Deserialize(ProtoBufDecodeError),
/// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)]
#[display("Error that occur during reading payload: {_0}")]
Payload(PayloadError),
}
// TODO: impl error for ProtoBufPayloadError
impl ResponseError for ProtoBufPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
@ -170,7 +175,9 @@ pub struct ProtoBufMessage<T: Message + Default> {
impl<T: Message + Default> ProtoBufMessage<T> {
/// Create `ProtoBufMessage` for request.
pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
if req.content_type() != "application/protobuf" {
if req.content_type() != "application/protobuf"
&& req.content_type() != "application/x-protobuf"
{
return ProtoBufMessage {
limit: 262_144,
length: None,
@ -262,14 +269,14 @@ impl ProtoBufResponseBuilder for HttpResponseBuilder {
value
.encode(&mut body)
.map_err(ProtoBufPayloadError::Serialize)?;
Ok(self.body(body))
}
}
#[cfg(test)]
mod tests {
use actix_web::http::header;
use actix_web::test::TestRequest;
use actix_web::{http::header, test::TestRequest};
use super::*;
@ -287,7 +294,7 @@ mod tests {
}
}
#[derive(Clone, PartialEq, Message)]
#[derive(Clone, PartialEq, Eq, Message)]
pub struct MyObject {
#[prost(int32, tag = "1")]
pub number: i32,

View File

@ -1,152 +0,0 @@
# Changes
## Unreleased - 2022-xx-xx
## 0.12.0 - 2022-07-09
- Update `actix` dependency to `0.13`.
- Update `redis-async` dependency to `0.13`.
- Update `tokio-util` dependency to `0.7`.
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.11.0 - 2022-03-15
### Removed
- `RedisSession` has been removed. Check out `RedisActorSessionStore` in `actix-session` for a session store backed by Redis using `actix-redis`. [#212]
### Changed
- Update `redis-async` dependency to `0.12`. [#212]
[#212]: https://github.com/actix/actix-extras/pull/212
## 0.10.0 - 2022-03-01
- Update `actix-web` dependency to `4`.
## 0.10.0-beta.6 - 2022-02-07
- Update `actix-web` dependency to `4.0.0-rc.1`.
## 0.10.0-beta.5 - 2021-12-29
- Update `actix-web` dependency to `4.0.0.beta-18`. [#218]
- Minimum supported Rust version (MSRV) is now 1.54.
[#218]: https://github.com/actix/actix-extras/pull/218
## 0.10.0-beta.4 - 2021-12-12
- A session will be created in Redis if and only if there is some data inside the session state. This reduces the performance impact of `RedisSession` on routes that do not leverage sessions. [#207]
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#207]: https://github.com/actix/actix-extras/pull/207
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.10.0-beta.3 - 2021-10-21
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.10.0-beta.2 - 2021-06-27
- No notable changes.
## 0.10.0-beta.1 - 2021-04-02
- Update `actix-web` dependency to 4.0.0 beta.
- Minimum supported Rust version (MSRV) is now 1.46.0.
## 0.9.2 - 2021-03-21
- Implement `std::error::Error` for `Error` [#135]
- Allow the removal of `Max-Age` for session-only cookies. [#161]
[#135]: https://github.com/actix/actix-extras/pull/135
[#161]: https://github.com/actix/actix-extras/pull/161
## 0.9.1 - 2020-09-12
- Enforce minimum redis-async version of 0.6.3 to workaround breaking patch change.
## 0.9.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimize `futures` dependency.
## 0.9.0-alpha.2 - 2020-05-17
- Add `cookie_http_only` functionality to RedisSession builder, setting this
to false allows JavaScript to access cookies. Defaults to true.
- Change type of parameter of ttl method to u32.
- Update `actix` to 0.10.0-alpha.3
- Update `tokio-util` to 0.3
- Minimum supported Rust version(MSRV) is now 1.40.0.
## 0.9.0-alpha.1 - 2020-03-28
- Update `actix` to 0.10.0-alpha.2
- Update `actix-session` to 0.4.0-alpha.1
- Update `actix-web` to 3.0.0-alpha.1
- Update `time` to 0.2.9
## 0.8.1 - 2020-02-18
- Move `env_logger` dependency to dev-dependencies and update to 0.7
- Update `actix_web` to 2.0.0 from 2.0.0-rc
- Move repository to actix-extras
## 0.8.0 - 2019-12-20
- Release
## 0.8.0-alpha.1 - 2019-12-16
- Migrate to actix 0.9
## 0.7.0 - 2019-09-25
- added cache_keygen functionality to RedisSession builder, enabling support for
customizable cache key creation
## 0.6.1 - 2019-07-19
- remove ClonableService usage
- added comprehensive tests for session workflow
## 0.6.0 - 2019-07-08
- actix-web 1.0.0 compatibility
- Upgraded logic that evaluates session state, including new SessionStatus field,
and introduced ``session.renew()`` and ``session.purge()`` functionality.
Use ``renew()`` to cycle the session key at successful login. ``renew()`` keeps a
session's state while replacing the old cookie and session key with new ones.
Use ``purge()`` at logout to invalidate the session cookie and remove the
session's redis cache entry.
## 0.5.1 - 2018-08-02
- Use cookie 0.11
## 0.5.0 - 2018-07-21
- Session cookie configuration
- Actix/Actix-web 0.7 compatibility
## 0.4.0 - 2018-05-08
- Actix web 0.6 compatibility
## 0.3.0 - 2018-04-10
- Actix web 0.5 compatibility
## 0.2.0 - 2018-02-28
- Use resolver actor from actix
- Use actix web 0.5
## 0.1.0 - 2018-01-23
- First release

View File

@ -1,47 +0,0 @@
[package]
name = "actix-redis"
version = "0.12.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Redis integration for Actix"
license = "MIT OR Apache-2.0"
keywords = ["actix", "redis", "async"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
categories = ["network-programming", "asynchronous"]
edition = "2018"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lib]
name = "actix_redis"
path = "src/lib.rs"
[features]
default = ["web"]
# actix-web integration
web = ["actix-web"]
[dependencies]
actix = { version = "0.13", default-features = false }
actix-rt = { version = "2.1", default-features = false }
actix-service = "2"
actix-tls = { version = "3", default-features = false, features = ["connect"] }
log = "0.4.6"
backoff = "0.4.0"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false }
redis-async = "0.13"
time = "0.3"
tokio = { version = "1.13.1", features = ["sync"] }
tokio-util = "0.7"
actix-web = { version = "4", default_features = false, optional = true }
[dev-dependencies]
actix-test = "0.1.0-beta.12"
actix-web = { version = "4", default_features = false, features = ["macros"] }
env_logger = "0.9"
serde = { version = "1.0.101", features = ["derive"] }

View File

@ -1,14 +0,0 @@
# actix-redis
> Redis integration for Actix.
[![crates.io](https://img.shields.io/crates/v/actix-redis?label=latest)](https://crates.io/crates/actix-redis)
[![Documentation](https://docs.rs/actix-redis/badge.svg?version=0.12.0)](https://docs.rs/actix-redis/0.12.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-redis)
[![Dependency Status](https://deps.rs/crate/actix-redis/0.12.0/status.svg)](https://deps.rs/crate/actix-redis/0.12.0)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-redis)
- [Example Project](https://github.com/actix/examples/tree/master/auth/redis-session)
- Minimum Supported Rust Version (MSRV): 1.57

View File

@ -1,30 +0,0 @@
//! Redis integration for `actix`.
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
mod redis;
use derive_more::{Display, Error, From};
pub use redis::{Command, RedisActor};
/// General purpose `actix-redis` error.
#[derive(Debug, Display, Error, From)]
pub enum Error {
#[display(fmt = "Redis error {}", _0)]
Redis(redis_async::error::Error),
/// Receiving message during reconnecting
#[display(fmt = "Redis: Not connected")]
NotConnected,
/// Cancel all waters when connection get dropped
#[display(fmt = "Redis: Disconnected")]
Disconnected,
}
#[cfg(feature = "web")]
impl actix_web::ResponseError for Error {}
// re-export
pub use redis_async::error::Error as RespError;
pub use redis_async::resp::RespValue;
pub use redis_async::resp_array;

View File

@ -1,141 +0,0 @@
use std::collections::VecDeque;
use std::io;
use actix::prelude::*;
use actix_rt::net::TcpStream;
use actix_service::boxed::{self, BoxService};
use actix_tls::connect::{ConnectError, ConnectInfo, Connection, ConnectorService};
use backoff::backoff::Backoff;
use backoff::ExponentialBackoff;
use log::{error, info, warn};
use redis_async::error::Error as RespError;
use redis_async::resp::{RespCodec, RespValue};
use tokio::io::{split, WriteHalf};
use tokio::sync::oneshot;
use tokio_util::codec::FramedRead;
use crate::Error;
/// Command for send data to Redis
#[derive(Debug)]
pub struct Command(pub RespValue);
impl Message for Command {
type Result = Result<RespValue, Error>;
}
/// Redis communication actor
pub struct RedisActor {
addr: String,
connector: BoxService<ConnectInfo<String>, Connection<String, TcpStream>, ConnectError>,
backoff: ExponentialBackoff,
cell: Option<actix::io::FramedWrite<RespValue, WriteHalf<TcpStream>, RespCodec>>,
queue: VecDeque<oneshot::Sender<Result<RespValue, Error>>>,
}
impl RedisActor {
/// Start new `Supervisor` with `RedisActor`.
pub fn start<S: Into<String>>(addr: S) -> Addr<RedisActor> {
let addr = addr.into();
let backoff = ExponentialBackoff {
max_elapsed_time: None,
..Default::default()
};
Supervisor::start(|_| RedisActor {
addr,
connector: boxed::service(ConnectorService::default()),
cell: None,
backoff,
queue: VecDeque::new(),
})
}
}
impl Actor for RedisActor {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Context<Self>) {
let req = ConnectInfo::new(self.addr.to_owned());
self.connector
.call(req)
.into_actor(self)
.map(|res, act, ctx| match res {
Ok(conn) => {
let stream = conn.into_parts().0;
info!("Connected to redis server: {}", act.addr);
let (r, w) = split(stream);
// configure write side of the connection
let framed = actix::io::FramedWrite::new(w, RespCodec, ctx);
act.cell = Some(framed);
// read side of the connection
ctx.add_stream(FramedRead::new(r, RespCodec));
act.backoff.reset();
}
Err(err) => {
error!("Can not connect to redis server: {}", err);
// re-connect with backoff time.
// we stop current context, supervisor will restart it.
if let Some(timeout) = act.backoff.next_backoff() {
ctx.run_later(timeout, |_, ctx| ctx.stop());
}
}
})
.wait(ctx);
}
}
impl Supervised for RedisActor {
fn restarting(&mut self, _: &mut Self::Context) {
self.cell.take();
for tx in self.queue.drain(..) {
let _ = tx.send(Err(Error::Disconnected));
}
}
}
impl actix::io::WriteHandler<io::Error> for RedisActor {
fn error(&mut self, err: io::Error, _: &mut Self::Context) -> Running {
warn!("Redis connection dropped: {} error: {}", self.addr, err);
Running::Stop
}
}
impl StreamHandler<Result<RespValue, RespError>> for RedisActor {
fn handle(&mut self, msg: Result<RespValue, RespError>, ctx: &mut Self::Context) {
match msg {
Err(e) => {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Err(e.into()));
}
ctx.stop();
}
Ok(val) => {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Ok(val));
}
}
}
}
}
impl Handler<Command> for RedisActor {
type Result = ResponseFuture<Result<RespValue, Error>>;
fn handle(&mut self, msg: Command, _: &mut Self::Context) -> Self::Result {
let (tx, rx) = oneshot::channel();
if let Some(ref mut cell) = self.cell {
self.queue.push_back(tx);
cell.write(msg.0);
} else {
let _ = tx.send(Err(Error::NotConnected));
}
Box::pin(async move { rx.await.map_err(|_| Error::Disconnected)? })
}
}

View File

@ -1,42 +0,0 @@
#[macro_use]
extern crate redis_async;
use actix_redis::{Command, Error, RedisActor, RespValue};
#[actix_web::test]
async fn test_error_connect() {
let addr = RedisActor::start("localhost:54000");
let _addr2 = addr.clone();
let res = addr.send(Command(resp_array!["GET", "test"])).await;
match res {
Ok(Err(Error::NotConnected)) => (),
_ => panic!("Should not happen {:?}", res),
}
}
#[actix_web::test]
async fn test_redis() {
env_logger::init();
let addr = RedisActor::start("127.0.0.1:6379");
let res = addr
.send(Command(resp_array!["SET", "test", "value"]))
.await;
match res {
Ok(Ok(resp)) => {
assert_eq!(resp, RespValue::SimpleString("OK".to_owned()));
let res = addr.send(Command(resp_array!["GET", "test"])).await;
match res {
Ok(Ok(resp)) => {
println!("RESP: {:?}", resp);
assert_eq!(resp, RespValue::BulkString((&b"value"[..]).into()));
}
_ => panic!("Should not happen {:?}", res),
}
}
_ => panic!("Should not happen {:?}", res),
}
}

View File

@ -1,9 +1,51 @@
# Changes
## Unreleased - 2021-xx-xx
## 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.
## 0.10.0
- Add `redis-session-rustls` crate feature that enables `rustls`-secured Redis sessions.
- Add `redis-pool` crate feature (off-by-default) which enables `RedisSessionStore::{new, builder}_pooled()` constructors.
- Rename `redis-rs-session` crate feature to `redis-session`.
- Rename `redis-rs-tls-session` crate feature to `redis-session-native-tls`.
- Remove `redis-actor-session` crate feature (and, therefore, the `actix-redis` based storage backend).
- Expose `storage::generate_session_key()`.
- Update `redis` dependency to `0.26`.
## 0.9.0
- Remove use of `async-trait` on `SessionStore` trait.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.8.0
- Set secure attribute when adding a session removal cookie.
- Update `redis` dependency to `0.23`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.7.2
- Set SameSite attribute when adding a session removal cookie. [#284]
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
[#284]: https://github.com/actix/actix-extras/pull/284
## 0.7.1
- Fix interaction between session state changes and renewal. [#265]
[#265]: https://github.com/actix/actix-extras/pull/265
## 0.7.0
## 0.7.0 - 2022-07-09
- Added `TtlExtensionPolicy` enum to support different strategies for extending the TTL attached to the session state. `TtlExtensionPolicy::OnEveryRequest` now allows for long-lived sessions that do not expire if the user remains active. [#233]
- `SessionLength` is now called `SessionLifecycle`. [#233]
- `SessionLength::Predetermined` is now called `SessionLifecycle::PersistentSession`. [#233]
@ -19,8 +61,8 @@
[#233]: https://github.com/actix/actix-extras/pull/233
## 0.6.2
## 0.6.2 - 2022-03-25
- Implement `SessionExt` for `GuardContext`. [#234]
- `RedisSessionStore` will prevent connection timeouts from causing user-visible errors. [#235]
- Do not leak internal implementation details to callers when errors occur. [#236]
@ -29,13 +71,14 @@
[#236]: https://github.com/actix/actix-extras/pull/236
[#235]: https://github.com/actix/actix-extras/pull/235
## 0.6.1
## 0.6.1 - 2022-03-21
- No significant changes since `0.6.0`.
## 0.6.0
## 0.6.0 - 2022-03-15
### Added
- `SessionMiddleware`, a middleware to provide support for saving/updating/deleting session state against a pluggable storage backend (see `SessionStore` trait). [#212]
- `CookieSessionStore`, a cookie-based backend to store session state. [#212]
- `RedisActorSessionStore`, a Redis-based backend to store session state powered by `actix-redis`. [#212]
@ -44,37 +87,39 @@
- Implement `SessionExt` for `ServiceResponse`. [#212]
### Changed
- Rename `UserSession` to `SessionExt`. [#212]
### Removed
- `CookieSession`; replaced with `CookieSessionStore`, a storage backend for `SessionMiddleware`. [#212]
- `Session::set_session`; use `Session::insert` to modify the session state. [#212]
[#212]: https://github.com/actix/actix-extras/pull/212
## 0.5.0
## 0.5.0 - 2022-03-01
- Update `actix-web` dependency to `4`.
## 0.5.0-beta.8
## 0.5.0-beta.8 - 2022-02-07
- Update `actix-web` dependency to `4.0.0-rc.1`.
## 0.5.0-beta.7
## 0.5.0-beta.7 - 2021-12-29
- Update `actix-web` dependency to `4.0.0.beta-18`. [#218]
- Minimum supported Rust version (MSRV) is now 1.54.
[#218]: https://github.com/actix/actix-extras/pull/218
## 0.5.0-beta.6
## 0.5.0-beta.6 - 2021-12-18
- Update `actix-web` dependency to `4.0.0.beta-15`. [#216]
[#216]: https://github.com/actix/actix-extras/pull/216
## 0.5.0-beta.5
## 0.5.0-beta.5 - 2021-12-12
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
- Remove `UserSession` implementation for `RequestHead`. [#209]
- A session will be created in the storage backend if and only if there is some data inside the session state. This reduces the performance impact of `SessionMiddleware` on routes that do not leverage sessions. [#207]
@ -82,12 +127,12 @@
[#207]: https://github.com/actix/actix-extras/pull/207
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.5.0-beta.4
## 0.5.0-beta.4 - 2021-11-22
- No significant changes since `0.5.0-beta.3`.
## 0.5.0-beta.3
## 0.5.0-beta.3 - 2021-10-21
- Impl `Clone` for `CookieSession`. [#201]
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
@ -95,12 +140,12 @@
[#201]: https://github.com/actix/actix-extras/pull/201
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.5.0-beta.2
## 0.5.0-beta.2 - 2021-06-27
- No notable changes.
## 0.5.0-beta.1
## 0.5.0-beta.1 - 2021-04-02
- Add `Session::entries`. [#170]
- Rename `Session::{set => insert}` to match standard hash map naming. [#170]
- Return values from `Session::remove`. [#170]
@ -112,21 +157,21 @@
[#170]: https://github.com/actix/actix-extras/pull/170
## 0.4.1
## 0.4.1 - 2021-03-21
- `Session::set_session` takes a `IntoIterator` instead of `Iterator`. [#105]
- Fix calls to `session.purge()` from paths other than the one specified in the cookie. [#129]
[#105]: https://github.com/actix/actix-extras/pull/105
[#129]: https://github.com/actix/actix-extras/pull/129
## 0.4.0
## 0.4.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimum supported Rust version (MSRV) is now 1.42.0.
## 0.4.0-alpha.1
## 0.4.0-alpha.1 - 2020-03-14
- Update the `time` dependency to 0.2.7
- Update the `actix-web` dependency to 3.0.0-alpha.1
- Long lasting auto-prolonged session [#1292]
@ -134,65 +179,62 @@
[#1292]: https://github.com/actix/actix-web/pull/1292
## 0.3.0 - 2019-12-20
- Release
## 0.3.0-alpha.4 - 2019-12-xx
- Allow access to sessions also from not mutable references to the request
## 0.3.0-alpha.3 - 2019-12-xx
- Add access to the session from RequestHead for use of session from guard methods
- Migrate to `std::future`
- Migrate to `actix-web` 2.0
## 0.2.0 - 2019-07-08
- Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()``
at successful login to cycle a session (new key/cookie but keeps state).
Use ``Session.purge()`` at logout to invalid a session cookie (and remove
from redis cache, if applicable).
- Enhanced `actix-session` to facilitate state changes. Use `Session.renew()` at successful login to cycle a session (new key/cookie but keeps state). Use `Session.purge()` at logout to invalid a session cookie (and remove from redis cache, if applicable).
## 0.1.1 - 2019-06-03
- Fix optional cookie session support
## 0.1.0 - 2019-05-18
- Use actix-web 1.0.0-rc
## 0.1.0-beta.4 - 2019-05-12
- Use actix-web 1.0.0-beta.4
## 0.1.0-beta.2 - 2019-04-28
- Add helper trait `UserSession` which allows to get session for ServiceRequest and HttpRequest
## 0.1.0-beta.1 - 2019-04-20
- Update actix-web to beta.1
- `CookieSession::max_age()` accepts value in seconds
## 0.1.0-alpha.6 - 2019-04-14
- Update actix-web alpha.6
## 0.1.0-alpha.4 - 2019-04-08
- Update actix-web
- Update actix-web
## 0.1.0-alpha.3 - 2019-04-02
- Update actix-web
## 0.1.0-alpha.2 - 2019-03-29
- Update actix-web
- Use new feature name for secure cookies
## 0.1.0-alpha.1 - 2019-03-28
- Initial impl

View File

@ -1,64 +1,60 @@
[package]
name = "actix-session"
version = "0.7.0"
version = "0.10.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>",
]
description = "Session management for Actix Web"
keywords = ["http", "web", "framework", "async", "session"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
license = "MIT OR Apache-2.0"
edition = "2018"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lib]
name = "actix_session"
path = "src/lib.rs"
all-features = true
[features]
default = []
cookie-session = []
redis-actor-session = ["actix-redis", "actix", "futures-core", "rand"]
redis-rs-session = ["redis", "rand"]
redis-rs-tls-session = ["redis-rs-session", "redis/tokio-native-tls-comp"]
redis-session = ["dep:redis"]
redis-session-native-tls = ["redis-session", "redis/tokio-native-tls-comp"]
redis-session-rustls = ["redis-session", "redis/tokio-rustls-comp"]
redis-pool = ["dep:deadpool-redis"]
[dependencies]
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4", default_features = false, features = ["cookies", "secure-cookies"] }
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
anyhow = "1"
async-trait = "0.1"
derive_more = "0.99.5"
rand = { version = "0.8", optional = true }
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-actor-session
actix = { version = "0.13", default-features = false, optional = true }
actix-redis = { version = "0.12", optional = true }
futures-core = { version = "0.3.7", default-features = false, optional = true }
# redis-rs-session
redis = { version = "0.21", default-features = false, features = ["aio", "tokio-comp", "connection-manager"], optional = true }
# redis-session
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-actor-session", "redis-rs-session"] }
actix-test = "0.1.0-beta.10"
actix-web = { version = "4", default_features = false, features = ["cookies", "secure-cookies", "macros"] }
env_logger = "0.9"
log = "0.4"
actix-session = { path = ".", features = ["cookie-session", "redis-session"] }
actix-test = "0.1"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies", "macros"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = "0.1.30"
[lints]
workspace = true
[[example]]
name = "basic"
required-features = ["redis-actor-session"]
required-features = ["redis-session"]
[[example]]
name = "authentication"
required-features = ["redis-actor-session"]
required-features = ["redis-session"]

View File

@ -1,14 +1,125 @@
# actix-session
> Session management for Actix Web applications.
> Session management for Actix Web.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session)
[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.7.0)](https://docs.rs/actix-session/0.7.0)
[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.10.1)](https://docs.rs/actix-session/0.10.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-session)
[![Dependency Status](https://deps.rs/crate/actix-session/0.7.0/status.svg)](https://deps.rs/crate/actix-session/0.7.0)
[![Dependency Status](https://deps.rs/crate/actix-session/0.10.1/status.svg)](https://deps.rs/crate/actix-session/0.10.1)
## Documentation & Resources
<!-- prettier-ignore-end -->
- [API Documentation](https://docs.rs/actix-session)
- [Example Projects](https://github.com/actix/examples/tree/master/auth/cookie-session)
- Minimum Supported Rust Version (MSRV): 1.57
<!-- cargo-rdme start -->
Session management for Actix Web.
The HTTP protocol, at a first glance, is stateless: the client sends a request, the server parses its content, performs some processing and returns a response. The outcome is only influenced by the provided inputs (i.e. the request content) and whatever state the server queries while performing its processing.
Stateless systems are easier to reason about, but they are not quite as powerful as we need them to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for every single request**. That is, for example, how 'Basic' Authentication works. While it may work for a machine user (i.e. an API client), it is impractical for a person—you do not want a login prompt on every single page you navigate to!
There is a solution - **sessions**. Using sessions the server can attach state to a set of requests coming from the same client. They are built on top of cookies - the server sets a cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the cookie and play it back to the server when sending new requests (using the `Cookie` header).
We refer to the cookie used for sessions as a **session cookie**. Its content is called **session key** (or **session ID**), while the state attached to the session is referred to as **session state**.
`actix-session` provides an easy-to-use framework to manage sessions in applications built on top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality provided by `actix-session`; it takes care of all the session cookie handling and instructs the **storage backend** to create/delete/update the session state based on the operations performed against the active [`Session`].
`actix-session` provides some built-in storage backends: ([`CookieSessionStore`], [`RedisSessionStore`]) - you can create a custom storage backend by implementing the [`SessionStore`] trait.
Further reading on sessions:
- [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265);
- [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html).
## Getting started
To start using sessions in your Actix Web application you must register [`SessionMiddleware`] as a middleware on your `App`:
```rust
use actix_web::{web, App, HttpServer, HttpResponse, Error};
use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
use actix_web::cookie::Key;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// When using `Key::generate()` it is important to initialize outside of the
// `HttpServer::new` closure. When deployed the secret key should be read from a
// configuration file or environment variables.
let secret_key = Key::generate();
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap();
HttpServer::new(move ||
App::new()
// Add session management to your application using Redis for session state storage
.wrap(
SessionMiddleware::new(
redis_store.clone(),
secret_key.clone(),
)
)
.default_service(web::to(|| HttpResponse::Ok())))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
```
The session state can be accessed and modified by your request handlers using the [`Session`] extractor. Note that this doesn't work in the stream of a streaming response.
```rust
use actix_web::Error;
use actix_session::Session;
fn index(session: Session) -> Result<&'static str, Error> {
// access the session state
if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count);
// modify the session state
session.insert("counter", count + 1)?;
} else {
session.insert("counter", 1)?;
}
Ok("Welcome!")
}
```
## Choosing A Backend
By default, `actix-session` does not provide any storage backend to retrieve and save the state attached to your sessions. You can enable:
- a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature flag.
```console
cargo add actix-session --features=cookie-session
```
- a Redis-based backend via the [`redis`] crate, [`RedisSessionStore`], using the `redis-session` feature flag.
```console
cargo add actix-session --features=redis-session
```
Add the `redis-session-native-tls` feature flag if you want to connect to Redis using a secure connection (via the `native-tls` crate):
```console
cargo add actix-session --features=redis-session-native-tls
```
If you, instead, prefer depending on `rustls`, use the `redis-session-rustls` feature flag:
```console
cargo add actix-session --features=redis-session-rustls
```
You can implement your own session storage backend using the [`SessionStore`] trait.
[`SessionStore`]: storage::SessionStore
[`CookieSessionStore`]: storage::CookieSessionStore
[`RedisSessionStore`]: storage::RedisSessionStore
<!-- cargo-rdme end -->

View File

@ -1,10 +1,12 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_session::{storage::RedisSessionStore, Session, SessionMiddleware};
use actix_web::{
cookie::{Key, SameSite},
error::InternalError,
middleware, web, App, Error, HttpResponse, HttpServer, Responder,
};
use serde::{Deserialize, Serialize};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
#[derive(Deserialize)]
struct Credentials {
@ -21,7 +23,7 @@ struct User {
impl User {
fn authenticate(credentials: Credentials) -> Result<Self, HttpResponse> {
// TODO: figure out why I keep getting hacked
// to do: figure out why I keep getting hacked /s
if &credentials.password != "hunter2" {
return Err(HttpResponse::Unauthorized().json("Unauthorized"));
}
@ -71,12 +73,21 @@ async fn secret(session: Session) -> Result<impl Responder, Error> {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
// The signing key would usually be read from a configuration file/environment variables.
let signing_key = Key::generate();
log::info!("starting HTTP server at http://localhost:8080");
tracing::info!("setting up Redis session storage");
let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
tracing::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(move || {
App::new()
@ -84,15 +95,12 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default())
// cookie session middleware
.wrap(
SessionMiddleware::builder(
RedisActorSessionStore::new("127.0.0.1:6379"),
signing_key.clone(),
)
// allow the cookie to be accessed from javascript
.cookie_http_only(false)
// allow the cookie only from the current domain
.cookie_same_site(SameSite::Strict)
.build(),
SessionMiddleware::builder(storage.clone(), signing_key.clone())
// allow the cookie to be accessed from javascript
.cookie_http_only(false)
// allow the cookie only from the current domain
.cookie_same_site(SameSite::Strict)
.build(),
)
.route("/login", web::post().to(login))
.route("/secret", web::get().to(secret))

View File

@ -1,13 +1,15 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_session::{storage::RedisSessionStore, Session, SessionMiddleware};
use actix_web::{cookie::Key, middleware, web, App, Error, HttpRequest, HttpServer, Responder};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
/// simple handler
async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Error> {
println!("{:?}", req);
println!("{req:?}");
// session
if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count);
println!("SESSION value: {count}");
session.insert("counter", count + 1)?;
} else {
session.insert("counter", 1)?;
@ -18,22 +20,28 @@ async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Err
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
// The signing key would usually be read from a configuration file/environment variables.
let signing_key = Key::generate();
log::info!("starting HTTP server at http://localhost:8080");
tracing::info!("setting up Redis session storage");
let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
tracing::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(move || {
App::new()
// enable logger
.wrap(middleware::Logger::default())
// cookie session middleware
.wrap(SessionMiddleware::new(
RedisActorSessionStore::new("127.0.0.1:6379"),
signing_key.clone(),
))
.wrap(SessionMiddleware::new(storage.clone(), signing_key.clone()))
// register simple route, handle all methods
.service(web::resource("/").to(index))
})

View File

@ -1,13 +1,14 @@
//! Configuration options to tune the behaviour of [`SessionMiddleware`].
use actix_web::cookie::{time::Duration, Key, SameSite};
use derive_more::derive::From;
use crate::{storage::SessionStore, SessionMiddleware};
/// Determines what type of session cookie should be used and how its lifecycle should be managed.
///
/// Used by [`SessionMiddlewareBuilder::session_lifecycle`].
#[derive(Debug, Clone)]
#[derive(Debug, Clone, From)]
#[non_exhaustive]
pub enum SessionLifecycle {
/// The session cookie will expire when the current browser session ends.
@ -27,18 +28,6 @@ pub enum SessionLifecycle {
PersistentSession(PersistentSession),
}
impl From<BrowserSession> for SessionLifecycle {
fn from(session: BrowserSession) -> Self {
Self::BrowserSession(session)
}
}
impl From<PersistentSession> for SessionLifecycle {
fn from(session: PersistentSession) -> Self {
Self::PersistentSession(session)
}
}
/// A [session lifecycle](SessionLifecycle) strategy where the session cookie expires when the
/// browser's current session ends.
///
@ -46,6 +35,9 @@ impl From<PersistentSession> for SessionLifecycle {
/// continue running in the background when the browser is closed—session cookies are not deleted
/// and they will still be available when the browser is opened again. Check the documentation of
/// the browsers you are targeting for up-to-date information.
///
/// Due to its `Into<SessionLifecycle>` implementation, a `BrowserSession` can be passed directly
/// to [`SessionMiddlewareBuilder::session_lifecycle()`].
#[derive(Debug, Clone)]
pub struct BrowserSession {
state_ttl: Duration,
@ -103,6 +95,26 @@ impl Default for BrowserSession {
/// Persistent cookies have a pre-determined expiration, specified via the `Max-Age` or `Expires`
/// attribute. They do not disappear when the current browser session ends.
///
/// Due to its `Into<SessionLifecycle>` implementation, a `PersistentSession` can be passed directly
/// to [`SessionMiddlewareBuilder::session_lifecycle()`].
///
/// # Examples
/// ```
/// use actix_web::cookie::time::Duration;
/// use actix_session::SessionMiddleware;
/// use actix_session::config::{PersistentSession, TtlExtensionPolicy};
///
/// const SECS_IN_WEEK: i64 = 60 * 60 * 24 * 7;
///
/// // a session lifecycle with a time-to-live (expiry) of 1 week and default extension policy
/// PersistentSession::default().session_ttl(Duration::seconds(SECS_IN_WEEK));
///
/// // a session lifecycle with the default time-to-live (expiry) and a custom extension policy
/// PersistentSession::default()
/// // this policy causes the session state's TTL to be refreshed on every request
/// .session_ttl_extension_policy(TtlExtensionPolicy::OnEveryRequest);
/// ```
///
/// [persistent]: https://www.whitehatsec.com/glossary/content/persistent-session-cookie
#[derive(Debug, Clone)]
pub struct PersistentSession {
@ -113,12 +125,13 @@ pub struct PersistentSession {
impl PersistentSession {
/// Specifies how long the session cookie should live.
///
/// Defaults to 1 day if left unspecified.
///
/// The session TTL is also used as the TTL for the session state in the storage backend.
///
/// Defaults to 1 day.
///
/// A persistent session can live more than the specified TTL if the TTL is extended.
/// See [`session_ttl_extension_policy`](Self::session_ttl_extension_policy) for more details.
#[doc(alias = "max_age", alias = "max age", alias = "expires")]
pub fn session_ttl(mut self, session_ttl: Duration) -> Self {
self.session_ttl = session_ttl;
self
@ -127,7 +140,7 @@ impl PersistentSession {
/// Determines under what circumstances the TTL of your session should be extended.
/// See [`TtlExtensionPolicy`] for more details.
///
/// Defaults to [`TtlExtensionPolicy::OnStateChanges`] if left unspecified.
/// Defaults to [`TtlExtensionPolicy::OnStateChanges`].
pub fn session_ttl_extension_policy(
mut self,
ttl_extension_policy: TtlExtensionPolicy,
@ -148,23 +161,23 @@ impl Default for PersistentSession {
/// Configuration for which events should trigger an extension of the time-to-live for your session.
///
/// If you are using a [`BrowserSession`], `TtlExtensionPolicy` controls how often the TTL of
/// the session state should be refreshed. The browser is in control of the lifecycle of the
/// session cookie.
/// If you are using a [`BrowserSession`], `TtlExtensionPolicy` controls how often the TTL of the
/// session state should be refreshed. The browser is in control of the lifecycle of the session
/// cookie.
///
/// If you are using a [`PersistentSession`], `TtlExtensionPolicy` controls both the expiration
/// of the session cookie and the TTL of the session state.
/// If you are using a [`PersistentSession`], `TtlExtensionPolicy` controls both the expiration of
/// the session cookie and the TTL of the session state on the storage backend.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum TtlExtensionPolicy {
/// The TTL is refreshed every time the server receives a request associated with a session.
///
/// # Performance impact
/// Refreshing the TTL on every request is not free.
/// It implies a refresh of the TTL on the session state. This translates into a request over
/// the network if you are using a remote system as storage backend (e.g. Redis).
/// This impacts both the total load on your storage backend (i.e. number of
/// queries it has to handle) and the latency of the requests served by your server.
/// Refreshing the TTL on every request is not free. It implies a refresh of the TTL on the
/// session state. This translates into a request over the network if you are using a remote
/// system as storage backend (e.g. Redis). This impacts both the total load on your storage
/// backend (i.e. number of queries it has to handle) and the latency of the requests served by
/// your server.
OnEveryRequest,
/// The TTL is refreshed every time the session state changes or the session key is renewed.
@ -197,8 +210,7 @@ pub(crate) const fn default_ttl_extension_policy() -> TtlExtensionPolicy {
TtlExtensionPolicy::OnStateChanges
}
/// A fluent builder to construct a [`SessionMiddleware`] instance with custom configuration
/// parameters.
/// A fluent, customized [`SessionMiddleware`] builder.
#[must_use]
pub struct SessionMiddlewareBuilder<Store: SessionStore> {
storage_backend: Store,
@ -236,6 +248,22 @@ impl<Store: SessionStore> SessionMiddlewareBuilder<Store> {
/// Check out [`SessionLifecycle`]'s documentation for more details on the available options.
///
/// Default is [`SessionLifecycle::BrowserSession`].
///
/// # Examples
/// ```
/// use actix_web::cookie::{Key, time::Duration};
/// use actix_session::{SessionMiddleware, config::PersistentSession};
/// use actix_session::storage::CookieSessionStore;
///
/// const SECS_IN_WEEK: i64 = 60 * 60 * 24 * 7;
///
/// // creates a session middleware with a time-to-live (expiry) of 1 week
/// SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&[0; 64]))
/// .session_lifecycle(
/// PersistentSession::default().session_ttl(Duration::seconds(SECS_IN_WEEK))
/// )
/// .build();
/// ```
pub fn session_lifecycle<S: Into<SessionLifecycle>>(mut self, session_lifecycle: S) -> Self {
match session_lifecycle.into() {
SessionLifecycle::BrowserSession(BrowserSession {

View File

@ -27,11 +27,11 @@
//! against the active [`Session`].
//!
//! `actix-session` provides some built-in storage backends: ([`CookieSessionStore`],
//! [`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend
//! by implementing the [`SessionStore`] trait.
//! [`RedisSessionStore`]) - you can create a custom storage backend by implementing the
//! [`SessionStore`] trait.
//!
//! Further reading on sessions:
//! - [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265);
//! - [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265);
//! - [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html).
//!
//! # Getting started
@ -40,21 +40,27 @@
//!
//! ```no_run
//! use actix_web::{web, App, HttpServer, HttpResponse, Error};
//! use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
//! use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
//! use actix_web::cookie::Key;
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! // The secret key would usually be read from a configuration file/environment variables.
//! // When using `Key::generate()` it is important to initialize outside of the
//! // `HttpServer::new` closure. When deployed the secret key should be read from a
//! // configuration file or environment variables.
//! let secret_key = Key::generate();
//! let redis_connection_string = "127.0.0.1:6379";
//!
//! let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
//! .await
//! .unwrap();
//!
//! HttpServer::new(move ||
//! App::new()
//! // Add session management to your application using Redis for session state storage
//! .wrap(
//! SessionMiddleware::new(
//! RedisActorSessionStore::new(redis_connection_string),
//! secret_key.clone()
//! redis_store.clone(),
//! secret_key.clone(),
//! )
//! )
//! .default_service(web::to(|| HttpResponse::Ok())))
@ -65,7 +71,7 @@
//! ```
//!
//! The session state can be accessed and modified by your request handlers using the [`Session`]
//! extractor.
//! extractor. Note that this doesn't work in the stream of a streaming response.
//!
//! ```no_run
//! use actix_web::Error;
@ -93,37 +99,28 @@
//! - a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature
//! flag.
//!
//! ```toml
//! [dependencies]
//! # ...
//! actix-session = { version = "...", features = ["cookie-session"] }
//! ```console
//! cargo add actix-session --features=cookie-session
//! ```
//!
//! - a Redis-based backend via [`actix-redis`](https://docs.rs/acitx-redis),
//! [`RedisActorSessionStore`], using the `redis-actor-session` feature flag.
//! - a Redis-based backend via the [`redis`] crate, [`RedisSessionStore`], using the
//! `redis-session` feature flag.
//!
//! ```toml
//! [dependencies]
//! # ...
//! actix-session = { version = "...", features = ["redis-actor-session"] }
//! ```console
//! cargo add actix-session --features=redis-session
//! ```
//!
//! - a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using
//! the `redis-rs-session` feature flag.
//! Add the `redis-session-native-tls` feature flag if you want to connect to Redis using a secure
//! connection (via the `native-tls` crate):
//!
//! ```toml
//! [dependencies]
//! # ...
//! actix-session = { version = "...", features = ["redis-rs-session"] }
//! ```console
//! cargo add actix-session --features=redis-session-native-tls
//! ```
//!
//! Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured
//! connection:
//! If you, instead, prefer depending on `rustls`, use the `redis-session-rustls` feature flag:
//!
//! ```toml
//! [dependencies]
//! # ...
//! actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] }
//! ```console
//! cargo add actix-session --features=redis-session-rustls
//! ```
//!
//! You can implement your own session storage backend using the [`SessionStore`] trait.
@ -131,14 +128,12 @@
//! [`SessionStore`]: storage::SessionStore
//! [`CookieSessionStore`]: storage::CookieSessionStore
//! [`RedisSessionStore`]: storage::RedisSessionStore
//! [`RedisActorSessionStore`]: storage::RedisActorSessionStore
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod config;
mod middleware;
@ -146,11 +141,14 @@ mod session;
mod session_ext;
pub mod storage;
pub use self::middleware::SessionMiddleware;
pub use self::session::{Session, SessionGetError, SessionInsertError, SessionStatus};
pub use self::session_ext::SessionExt;
pub use self::{
middleware::SessionMiddleware,
session::{Session, SessionGetError, SessionInsertError, SessionStatus},
session_ext::SessionExt,
};
#[cfg(test)]
#[allow(missing_docs)]
pub mod test_helpers {
use actix_web::cookie::Key;
@ -177,7 +175,7 @@ pub mod test_helpers {
CookieContentSecurity::Signed,
CookieContentSecurity::Private,
] {
println!("Using {:?} as cookie content security policy.", policy);
println!("Using {policy:?} as cookie content security policy.");
acceptance_tests::basic_workflow(store_builder.clone(), *policy).await;
acceptance_tests::expiration_is_refreshed_on_changes(store_builder.clone(), *policy)
.await;
@ -207,9 +205,11 @@ pub mod test_helpers {
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::config::{CookieContentSecurity, PersistentSession, TtlExtensionPolicy};
use crate::{
storage::SessionStore, test_helpers::key, Session, SessionExt, SessionMiddleware,
config::{CookieContentSecurity, PersistentSession, TtlExtensionPolicy},
storage::SessionStore,
test_helpers::key,
Session, SessionExt, SessionMiddleware,
};
pub(super) async fn basic_workflow<F, Store>(
@ -239,7 +239,7 @@ pub mod test_helpers {
}))
.service(web::resource("/test/").to(|ses: Session| async move {
let val: usize = ses.get("counter").unwrap().unwrap();
format!("counter: {}", val)
format!("counter: {val}")
})),
)
.await;
@ -648,7 +648,7 @@ pub mod test_helpers {
);
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexResponse {
user_id: Option<String>,
counter: i32,
@ -670,7 +670,7 @@ pub mod test_helpers {
.get::<i32>("counter")
.unwrap_or(Some(0))
.map_or(1, |inner| inner + 1);
session.insert("counter", &counter)?;
session.insert("counter", counter)?;
Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter }))
}
@ -706,9 +706,9 @@ pub mod test_helpers {
async fn logout(session: Session) -> Result<HttpResponse> {
let id: Option<String> = session.get("user_id")?;
let body = if let Some(x) = id {
let body = if let Some(id) = id {
session.purge();
format!("Logged out: {}", x)
format!("Logged out: {id}")
} else {
"Could not log out anonymous user".to_owned()
};
@ -722,10 +722,7 @@ pub mod test_helpers {
impl ServiceResponseExt for ServiceResponse {
fn get_cookie(&self, cookie_name: &str) -> Option<actix_web::cookie::Cookie<'_>> {
self.response()
.cookies()
.into_iter()
.find(|c| c.name() == cookie_name)
self.response().cookies().find(|c| c.name() == cookie_name)
}
}
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, convert::TryInto, fmt, future::Future, pin::Pin, rc::Rc};
use std::{collections::HashMap, fmt, future::Future, pin::Pin, rc::Rc};
use actix_utils::future::{ready, Ready};
use actix_web::{
@ -35,9 +35,19 @@ use crate::{
/// [`SessionStore`]);
/// - a secret key, to sign or encrypt the content of client-side session cookie.
///
/// # How did we choose defaults?
/// You should not regret adding `actix-session` to your dependencies and going to production using
/// the default configuration. That is why, when in doubt, we opt to use the most secure option for
/// each configuration parameter.
///
/// We expose knobs to change the default to suit your needs—i.e., if you know what you are doing,
/// we will not stop you. But being a subject-matter expert should not be a requirement to deploy
/// reasonably secure implementation of sessions.
///
/// # Examples
/// ```no_run
/// use actix_web::{web, App, HttpServer, HttpResponse, Error};
/// use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
/// use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
/// use actix_web::cookie::Key;
///
/// // The secret key would usually be read from a configuration file/environment variables.
@ -49,20 +59,20 @@ use crate::{
/// #[actix_web::main]
/// async fn main() -> std::io::Result<()> {
/// let secret_key = get_secret_key();
/// let redis_connection_string = "127.0.0.1:6379";
/// HttpServer::new(move ||
/// App::new()
/// // Add session management to your application using Redis for session state storage
/// .wrap(
/// SessionMiddleware::new(
/// RedisActorSessionStore::new(redis_connection_string),
/// secret_key.clone()
/// )
/// )
/// .default_service(web::to(|| HttpResponse::Ok())))
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
/// let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
///
/// HttpServer::new(move || {
/// App::new()
/// // Add session management to your application using Redis as storage
/// .wrap(SessionMiddleware::new(
/// storage.clone(),
/// secret_key.clone(),
/// ))
/// .default_service(web::to(|| HttpResponse::Ok()))
/// })
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
/// }
/// ```
///
@ -70,7 +80,7 @@ use crate::{
///
/// ```no_run
/// use actix_web::{App, cookie::{Key, time}, Error, HttpResponse, HttpServer, web};
/// use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
/// use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
/// use actix_session::config::PersistentSession;
///
/// // The secret key would usually be read from a configuration file/environment variables.
@ -82,37 +92,25 @@ use crate::{
/// #[actix_web::main]
/// async fn main() -> std::io::Result<()> {
/// let secret_key = get_secret_key();
/// let redis_connection_string = "127.0.0.1:6379";
/// HttpServer::new(move ||
/// App::new()
/// let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
///
/// HttpServer::new(move || {
/// App::new()
/// // Customise session length!
/// .wrap(
/// SessionMiddleware::builder(
/// RedisActorSessionStore::new(redis_connection_string),
/// secret_key.clone()
/// )
/// .session_lifecycle(
/// PersistentSession::default()
/// .session_ttl(time::Duration::days(5))
/// )
/// .build(),
/// SessionMiddleware::builder(storage.clone(), secret_key.clone())
/// .session_lifecycle(
/// PersistentSession::default().session_ttl(time::Duration::days(5)),
/// )
/// .build(),
/// )
/// .default_service(web::to(|| HttpResponse::Ok())))
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
/// .default_service(web::to(|| HttpResponse::Ok()))
/// })
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
/// }
/// ```
///
/// ## How did we choose defaults?
///
/// You should not regret adding `actix-session` to your dependencies and going to production using
/// the default configuration. That is why, when in doubt, we opt to use the most secure option for
/// each configuration parameter.
///
/// We expose knobs to change the default to suit your needs—i.e., if you know what you are doing,
/// we will not stop you. But being a subject-matter expert should not be a requirement to deploy
/// reasonably secure implementation of sessions.
#[derive(Clone)]
pub struct SessionMiddleware<Store: SessionStore> {
storage_backend: Rc<Store>,
@ -125,7 +123,7 @@ impl<Store: SessionStore> SessionMiddleware<Store> {
///
/// To create a new instance of [`SessionMiddleware`] you need to provide:
/// - an instance of the session storage backend you wish to use (i.e. an implementation of
/// [`SessionStore]);
/// [`SessionStore`]);
/// - a secret key, to sign or encrypt the content of client-side session cookie.
pub fn new(store: Store, key: Key) -> Self {
Self::builder(store, key).build()
@ -135,7 +133,7 @@ impl<Store: SessionStore> SessionMiddleware<Store> {
///
/// It takes as input the two required inputs to create a new instance of [`SessionMiddleware`]:
/// - an instance of the session storage backend you wish to use (i.e. an implementation of
/// [`SessionStore]);
/// [`SessionStore`]);
/// - a secret key, to sign or encrypt the content of client-side session cookie.
pub fn builder(store: Store, key: Key) -> SessionMiddlewareBuilder<Store> {
SessionMiddlewareBuilder::new(store, config::default_configuration(key))
@ -444,7 +442,9 @@ fn delete_session_cookie(
) -> Result<(), anyhow::Error> {
let removal_cookie = Cookie::build(config.name.clone(), "")
.path(config.path.clone())
.http_only(config.http_only);
.secure(config.secure)
.http_only(config.http_only)
.same_site(config.same_site);
let mut removal_cookie = if let Some(ref domain) = config.domain {
removal_cookie.domain(domain)

View File

@ -14,7 +14,7 @@ use actix_web::{
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
};
use anyhow::Context;
use derive_more::{Display, From};
use derive_more::derive::{Display, From};
use serde::{de::DeserializeOwned, Serialize};
/// The primary interface to access and modify session state.
@ -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);
@ -46,7 +49,7 @@ use serde::{de::DeserializeOwned, Serialize};
pub struct Session(Rc<RefCell<SessionInner>>);
/// Status of a [`Session`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum SessionStatus {
/// Session state has been updated - the changes will have to be persisted to the backend.
Changed,
@ -64,15 +67,10 @@ pub enum SessionStatus {
Renewed,
/// The session state has not been modified since its creation/retrieval.
#[default]
Unchanged,
}
impl Default for SessionStatus {
fn default() -> SessionStatus {
SessionStatus::Unchanged
}
}
#[derive(Default)]
struct SessionInner {
state: HashMap<String, String>,
@ -102,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.
@ -119,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>,
@ -128,16 +133,17 @@ impl Session {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed;
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
let key = key.into();
let val = serde_json::to_string(&value)
.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)?;
@ -148,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.
@ -155,7 +238,9 @@ impl Session {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed;
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
return inner.state.remove(key);
}
@ -187,7 +272,9 @@ impl Session {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed;
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
inner.state.clear()
}
}
@ -212,11 +299,12 @@ impl Session {
///
/// Values that match keys already existing on the session will be overwritten. Values should
/// already be JSON serialized.
#[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn set_session(
req: &mut ServiceRequest,
data: impl IntoIterator<Item = (String, String)>,
) {
let session = Session::get_session(&mut *req.extensions_mut());
let session = Session::get_session(&mut req.extensions_mut());
let mut inner = session.0.borrow_mut();
inner.state.extend(data);
}
@ -226,6 +314,7 @@ impl Session {
/// This is a destructive operation - the session state is removed from the request extensions
/// typemap, leaving behind a new empty map. It should only be used when the session is being
/// finalised (i.e. in `SessionMiddleware`).
#[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn get_changes<B>(
res: &mut ServiceResponse<B>,
) -> (SessionStatus, HashMap<String, String>) {
@ -279,13 +368,13 @@ impl FromRequest for Session {
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(Ok(Session::get_session(&mut *req.extensions_mut())))
ready(Ok(Session::get_session(&mut req.extensions_mut())))
}
}
/// Error returned by [`Session::get`].
#[derive(Debug, Display, From)]
#[display(fmt = "{}", _0)]
#[display("{_0}")]
pub struct SessionGetError(anyhow::Error);
impl StdError for SessionGetError {
@ -302,7 +391,7 @@ impl ResponseError for SessionGetError {
/// Error returned by [`Session::insert`].
#[derive(Debug, Display, From)]
#[display(fmt = "{}", _0)]
#[display("{_0}")]
pub struct SessionInsertError(anyhow::Error);
impl StdError for SessionInsertError {
@ -316,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())
}
}

View File

@ -15,13 +15,13 @@ pub trait SessionExt {
impl SessionExt for HttpRequest {
fn get_session(&self) -> Session {
Session::get_session(&mut *self.extensions_mut())
Session::get_session(&mut self.extensions_mut())
}
}
impl SessionExt for ServiceRequest {
fn get_session(&self) -> Session {
Session::get_session(&mut *self.extensions_mut())
Session::get_session(&mut self.extensions_mut())
}
}
@ -31,8 +31,8 @@ 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())
Session::get_session(&mut self.req_data_mut())
}
}

View File

@ -1,5 +1,3 @@
use std::convert::TryInto;
use actix_web::cookie::time::Duration;
use anyhow::Error;
@ -47,12 +45,10 @@ use crate::storage::{
/// storage backend.
///
/// [`CookieContentSecurity::Private`]: crate::config::CookieContentSecurity::Private
#[cfg_attr(docsrs, doc(cfg(feature = "cookie-session")))]
#[derive(Default)]
#[non_exhaustive]
pub struct CookieSessionStore;
#[async_trait::async_trait(?Send)]
impl SessionStore for CookieSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
serde_json::from_str(session_key.as_ref())
@ -70,10 +66,10 @@ impl SessionStore for CookieSessionStore {
.map_err(anyhow::Error::new)
.map_err(SaveError::Serialization)?;
Ok(session_key
session_key
.try_into()
.map_err(Into::into)
.map_err(SaveError::Other)?)
.map_err(SaveError::Other)
}
async fn update(

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::{collections::HashMap, future::Future};
use actix_web::cookie::time::Duration;
use derive_more::Display;
use derive_more::derive::Display;
use super::SessionKey;
@ -10,41 +10,39 @@ pub(crate) type SessionState = HashMap<String, String>;
/// The interface to retrieve and save the current session data from/to the chosen storage backend.
///
/// You can provide your own custom session store backend by implementing this trait.
///
/// [`async-trait`](https://docs.rs/async-trait) is used for this trait's definition. Therefore, it
/// is required for implementations, too. In particular, we use the send-optional variant:
/// `#[async_trait(?Send)]`.
#[async_trait::async_trait(?Send)]
pub trait SessionStore {
/// Loads the session state associated to a session key.
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError>;
fn load(
&self,
session_key: &SessionKey,
) -> impl Future<Output = Result<Option<SessionState>, LoadError>>;
/// Persist the session state for a newly created session.
///
/// Returns the corresponding session key.
async fn save(
fn save(
&self,
session_state: SessionState,
ttl: &Duration,
) -> Result<SessionKey, SaveError>;
) -> impl Future<Output = Result<SessionKey, SaveError>>;
/// Updates the session state associated to a pre-existing session key.
async fn update(
fn update(
&self,
session_key: SessionKey,
session_state: SessionState,
ttl: &Duration,
) -> Result<SessionKey, UpdateError>;
) -> impl Future<Output = Result<SessionKey, UpdateError>>;
/// Updates the TTL of the session state associated to a pre-existing session key.
async fn update_ttl(
fn update_ttl(
&self,
session_key: &SessionKey,
ttl: &Duration,
) -> Result<(), anyhow::Error>;
) -> impl Future<Output = Result<(), anyhow::Error>>;
/// Deletes a session from the store.
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error>;
fn delete(&self, session_key: &SessionKey) -> impl Future<Output = Result<(), anyhow::Error>>;
}
// We cannot derive the `Error` implementation using `derive_more` for our custom errors:
@ -55,11 +53,11 @@ pub trait SessionStore {
#[derive(Debug, Display)]
pub enum LoadError {
/// Failed to deserialize session state.
#[display(fmt = "Failed to deserialize session state")]
#[display("Failed to deserialize session state")]
Deserialization(anyhow::Error),
/// Something went wrong when retrieving the session state.
#[display(fmt = "Something went wrong when retrieving the session state")]
#[display("Something went wrong when retrieving the session state")]
Other(anyhow::Error),
}
@ -76,11 +74,11 @@ impl std::error::Error for LoadError {
#[derive(Debug, Display)]
pub enum SaveError {
/// Failed to serialize session state.
#[display(fmt = "Failed to serialize session state")]
#[display("Failed to serialize session state")]
Serialization(anyhow::Error),
/// Something went wrong when persisting the session state.
#[display(fmt = "Something went wrong when persisting the session state")]
#[display("Something went wrong when persisting the session state")]
Other(anyhow::Error),
}
@ -97,11 +95,11 @@ impl std::error::Error for SaveError {
/// Possible failures modes for [`SessionStore::update`].
pub enum UpdateError {
/// Failed to serialize session state.
#[display(fmt = "Failed to serialize session state")]
#[display("Failed to serialize session state")]
Serialization(anyhow::Error),
/// Something went wrong when updating the session state.
#[display(fmt = "Something went wrong when updating the session state.")]
#[display("Something went wrong when updating the session state.")]
Other(anyhow::Error),
}

View File

@ -1,26 +1,19 @@
//! Pluggable storage backends for session state.
mod interface;
mod session_key;
pub use self::interface::{LoadError, SaveError, SessionStore, UpdateError};
pub use self::session_key::SessionKey;
#[cfg(feature = "cookie-session")]
mod cookie;
#[cfg(feature = "redis-actor-session")]
mod redis_actor;
#[cfg(feature = "redis-rs-session")]
mod interface;
#[cfg(feature = "redis-session")]
mod redis_rs;
#[cfg(any(feature = "redis-actor-session", feature = "redis-rs-session"))]
mod session_key;
mod utils;
#[cfg(feature = "cookie-session")]
pub use cookie::CookieSessionStore;
#[cfg(feature = "redis-actor-session")]
pub use redis_actor::{RedisActorSessionStore, RedisActorSessionStoreBuilder};
#[cfg(feature = "redis-rs-session")]
pub use redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
pub use self::cookie::CookieSessionStore;
#[cfg(feature = "redis-session")]
pub use self::redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
pub use self::{
interface::{LoadError, SaveError, SessionStore, UpdateError},
session_key::SessionKey,
utils::generate_session_key,
};

View File

@ -53,7 +53,6 @@ use crate::storage::{
/// Redis. Use [`RedisSessionStore`] if you need TLS support.
///
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
#[cfg_attr(docsrs, doc(cfg(feature = "redis-actor-session")))]
pub struct RedisActorSessionStore {
configuration: CacheConfiguration,
addr: Addr<RedisActor>,
@ -93,7 +92,6 @@ impl Default for CacheConfiguration {
/// A fluent builder to construct a [`RedisActorSessionStore`] instance with custom configuration
/// parameters.
#[cfg_attr(docsrs, doc(cfg(feature = "redis-actor-session")))]
#[must_use]
pub struct RedisActorSessionStoreBuilder {
connection_string: String,
@ -120,7 +118,6 @@ impl RedisActorSessionStoreBuilder {
}
}
#[async_trait::async_trait(?Send)]
impl SessionStore for RedisActorSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
@ -280,8 +277,6 @@ impl SessionStore for RedisActorSessionStore {
mod tests {
use std::collections::HashMap;
use actix_web::cookie::time::Duration;
use super::*;
use crate::test_helpers::acceptance_test_suite;

View File

@ -1,8 +1,8 @@
use std::{convert::TryInto, sync::Arc};
use std::sync::Arc;
use actix_web::cookie::time::Duration;
use anyhow::{Context, Error};
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};
use anyhow::Error;
use redis::{aio::ConnectionManager, AsyncCommands, Client, Cmd, FromRedisValue, Value};
use super::SessionKey;
use crate::storage::{
@ -44,7 +44,7 @@ use crate::storage::{
/// ```
///
/// # TLS support
/// Add the `redis-rs-tls-session` 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
@ -56,15 +56,38 @@ use crate::storage::{
/// # })
/// ```
///
/// # Implementation notes
/// `RedisSessionStore` leverages [`redis-rs`] as Redis client.
/// # Pooled Redis Connections
///
/// [`redis-rs`]: https://github.com/mitsuhiko/redis-rs
#[cfg_attr(docsrs, doc(cfg(feature = "redis-rs-session")))]
/// When the `redis-pool` crate feature is enabled, a pre-existing pool from [`deadpool_redis`] can
/// be provided.
///
/// ```no_run
/// use actix_session::storage::RedisSessionStore;
/// use deadpool_redis::{Config, Runtime};
///
/// let redis_cfg = Config::from_url("redis://127.0.0.1:6379");
/// let redis_pool = redis_cfg.create_pool(Some(Runtime::Tokio1)).unwrap();
///
/// let store = RedisSessionStore::new_pooled(redis_pool);
/// ```
///
/// # Implementation notes
///
/// `RedisSessionStore` leverages the [`redis`] crate as the underlying Redis client.
#[derive(Clone)]
pub struct RedisSessionStore {
configuration: CacheConfiguration,
client: ConnectionManager,
client: RedisSessionConn,
}
#[derive(Clone)]
enum RedisSessionConn {
/// Single connection.
Single(ConnectionManager),
/// Connection pool.
#[cfg(feature = "redis-pool")]
Pool(deadpool_redis::Pool),
}
#[derive(Clone)]
@ -81,35 +104,77 @@ impl Default for CacheConfiguration {
}
impl RedisSessionStore {
/// A fluent API to configure [`RedisSessionStore`].
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
pub fn builder<S: Into<String>>(connection_string: S) -> RedisSessionStoreBuilder {
/// Returns a fluent API builder to configure [`RedisSessionStore`].
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a connection string for Redis.
pub fn builder(connection_string: impl Into<String>) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
connection_string: connection_string.into(),
conn_builder: RedisSessionConnBuilder::Single(connection_string.into()),
}
}
/// Create a new instance of [`RedisSessionStore`] using the default configuration.
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
pub async fn new<S: Into<String>>(
connection_string: S,
) -> Result<RedisSessionStore, anyhow::Error> {
/// Returns a fluent API builder to configure [`RedisSessionStore`].
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a pool object for Redis.
#[cfg(feature = "redis-pool")]
pub fn builder_pooled(pool: impl Into<deadpool_redis::Pool>) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
conn_builder: RedisSessionConnBuilder::Pool(pool.into()),
}
}
/// Creates a new instance of [`RedisSessionStore`] using the default configuration.
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a connection string for Redis.
pub async fn new(connection_string: impl Into<String>) -> Result<RedisSessionStore, Error> {
Self::builder(connection_string).build().await
}
/// Creates a new instance of [`RedisSessionStore`] using the default configuration.
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a pool object for Redis.
#[cfg(feature = "redis-pool")]
pub async fn new_pooled(
pool: impl Into<deadpool_redis::Pool>,
) -> anyhow::Result<RedisSessionStore> {
Self::builder_pooled(pool).build().await
}
}
/// A fluent builder to construct a [`RedisSessionStore`] instance with custom configuration
/// parameters.
///
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
#[cfg_attr(docsrs, doc(cfg(feature = "redis-rs-session")))]
#[must_use]
pub struct RedisSessionStoreBuilder {
connection_string: String,
configuration: CacheConfiguration,
conn_builder: RedisSessionConnBuilder,
}
enum RedisSessionConnBuilder {
/// Single connection string.
Single(String),
/// Pre-built connection pool.
#[cfg(feature = "redis-pool")]
Pool(deadpool_redis::Pool),
}
impl RedisSessionConnBuilder {
async fn into_client(self) -> anyhow::Result<RedisSessionConn> {
Ok(match self {
RedisSessionConnBuilder::Single(conn_string) => {
RedisSessionConn::Single(ConnectionManager::new(Client::open(conn_string)?).await?)
}
#[cfg(feature = "redis-pool")]
RedisSessionConnBuilder::Pool(pool) => RedisSessionConn::Pool(pool),
})
}
}
impl RedisSessionStoreBuilder {
@ -122,11 +187,10 @@ impl RedisSessionStoreBuilder {
self
}
/// Finalise the builder and return a [`RedisActorSessionStore`] instance.
///
/// [`RedisActorSessionStore`]: crate::storage::RedisActorSessionStore
pub async fn build(self) -> Result<RedisSessionStore, anyhow::Error> {
let client = ConnectionManager::new(redis::Client::open(self.connection_string)?).await?;
/// Finalises builder and returns a [`RedisSessionStore`] instance.
pub async fn build(self) -> anyhow::Result<RedisSessionStore> {
let client = self.conn_builder.into_client().await?;
Ok(RedisSessionStore {
configuration: self.configuration,
client,
@ -134,7 +198,6 @@ impl RedisSessionStoreBuilder {
}
}
#[async_trait::async_trait(?Send)]
impl SessionStore for RedisSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
@ -142,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 {
@ -164,15 +226,19 @@ impl SessionStore for RedisSessionStore {
let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
"NX", // NX: only set the key if it does not already exist
"EX", // EX: set expiry
&format!("{}", ttl.whole_seconds()),
]))
self.execute_command::<()>(
redis::cmd("SET")
.arg(&[
&cache_key, // key
&body, // value
"NX", // only set the key if it does not already exist
"EX", // set expiry / TTL
])
.arg(
ttl.whole_seconds(), // EXpiry in seconds
),
)
.await
.map_err(Into::into)
.map_err(SaveError::Other)?;
Ok(session_key)
@ -190,7 +256,7 @@ impl SessionStore for RedisSessionStore {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let v: redis::Value = self
let v: Value = self
.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
@ -199,7 +265,6 @@ impl SessionStore for RedisSessionStore {
&format!("{}", ttl.whole_seconds()),
]))
.await
.map_err(Into::into)
.map_err(UpdateError::Other)?;
match v {
@ -215,7 +280,7 @@ impl SessionStore for RedisSessionStore {
SaveError::Other(err) => UpdateError::Other(err),
})
}
Value::Int(_) | Value::Okay | Value::Status(_) => Ok(session_key),
Value::Int(_) | Value::Okay | Value::SimpleString(_) => Ok(session_key),
val => Err(UpdateError::Other(anyhow::anyhow!(
"Failed to update session state. {:?}",
val
@ -223,26 +288,33 @@ impl SessionStore for RedisSessionStore {
}
}
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> anyhow::Result<()> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.client
.clone()
.expire(
&cache_key,
ttl.whole_seconds().try_into().context(
"Failed to convert the state TTL into the number of whole seconds remaining",
)?,
)
.await?;
match self.client {
RedisSessionConn::Single(ref conn) => {
conn.clone()
.expire::<_, ()>(&cache_key, ttl.whole_seconds())
.await?;
}
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
pool.get()
.await?
.expire::<_, ()>(&cache_key, ttl.whole_seconds())
.await?;
}
}
Ok(())
}
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
async fn delete(&self, session_key: &SessionKey) -> Result<(), Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.execute_command(redis::cmd("DEL").arg(&[&cache_key]))
self.execute_command::<()>(redis::cmd("DEL").arg(&[&cache_key]))
.await
.map_err(Into::into)
.map_err(UpdateError::Other)?;
Ok(())
@ -263,24 +335,56 @@ impl RedisSessionStore {
/// This helper method catches this case (`.is_connection_dropped`) to execute a retry. The
/// retry will be executed on a fresh connection, therefore it is likely to succeed (or fail for
/// a different more meaningful reason).
async fn execute_command<T: FromRedisValue>(&self, cmd: &mut Cmd) -> RedisResult<T> {
#[allow(clippy::needless_pass_by_ref_mut)]
async fn execute_command<T: FromRedisValue>(&self, cmd: &mut Cmd) -> anyhow::Result<T> {
let mut can_retry = true;
loop {
match cmd.query_async(&mut self.client.clone()).await {
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
tracing::debug!(
"Connection dropped while trying to talk to Redis. Retrying."
);
match self.client {
RedisSessionConn::Single(ref conn) => {
let mut conn = conn.clone();
// Retry at most once
can_retry = false;
loop {
match cmd.query_async(&mut conn).await {
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
tracing::debug!(
"Connection dropped while trying to talk to Redis. Retrying."
);
continue;
} else {
return Err(err);
// Retry at most once
can_retry = false;
continue;
} else {
return Err(err.into());
}
}
}
}
}
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
let mut conn = pool.get().await?;
loop {
match cmd.query_async(&mut conn).await {
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
tracing::debug!(
"Connection dropped while trying to talk to Redis. Retrying."
);
// Retry at most once
can_retry = false;
continue;
} else {
return Err(err.into());
}
}
}
}
}
@ -293,15 +397,27 @@ mod tests {
use std::collections::HashMap;
use actix_web::cookie::time;
use redis::AsyncCommands;
#[cfg(not(feature = "redis-session"))]
use deadpool_redis::{Config, Runtime};
use super::*;
use crate::test_helpers::acceptance_test_suite;
async fn redis_store() -> RedisSessionStore {
RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap()
#[cfg(feature = "redis-session")]
{
RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap()
}
#[cfg(not(feature = "redis-session"))]
{
let redis_pool = Config::from_url("redis://127.0.0.1:6379")
.create_pool(Some(Runtime::Tokio1))
.unwrap();
RedisSessionStore::new(redis_pool.clone())
}
}
#[actix_web::test]
@ -321,12 +437,25 @@ mod tests {
async fn loading_an_invalid_session_state_returns_deserialization_error() {
let store = redis_store().await;
let session_key = generate_session_key();
store
.client
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap();
match store.client {
RedisSessionConn::Single(ref conn) => conn
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap(),
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
pool.get()
.await
.unwrap()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap();
}
}
assert!(matches!(
store.load(&session_key).await.unwrap_err(),
LoadError::Deserialization(_),

View File

@ -1,6 +1,4 @@
use std::convert::TryFrom;
use derive_more::{Display, From};
use derive_more::derive::{Display, From};
/// A session key, the string stored in a client-side cookie to associate a user with its session
/// state on the backend.
@ -9,8 +7,7 @@ use derive_more::{Display, From};
/// Session keys are stored as cookies, therefore they cannot be arbitrary long. Session keys are
/// required to be smaller than 4064 bytes.
///
/// ```rust
/// # use std::convert::TryInto;
/// ```
/// use actix_session::storage::SessionKey;
///
/// let key: String = std::iter::repeat('a').take(4065).collect();
@ -48,7 +45,7 @@ impl From<SessionKey> for String {
}
#[derive(Debug, Display, From)]
#[display(fmt = "The provided string is not a valid session key")]
#[display("The provided string is not a valid session key")]
pub struct InvalidSessionKeyError(anyhow::Error);
impl std::error::Error for InvalidSessionKeyError {

View File

@ -1,19 +1,13 @@
use std::convert::TryInto;
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng as _};
use rand::distr::{Alphanumeric, SampleString as _};
use crate::storage::SessionKey;
/// Session key generation routine that follows [OWASP recommendations].
///
/// [OWASP recommendations]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
pub(crate) fn generate_session_key() -> SessionKey {
let value = std::iter::repeat(())
.map(|()| OsRng.sample(Alphanumeric))
.take(64)
.collect::<Vec<_>>();
// These unwraps will never panic because pre-conditions are always verified
// (i.e. length and character set)
String::from_utf8(value).unwrap().try_into().unwrap()
pub fn generate_session_key() -> SessionKey {
Alphanumeric
.sample_string(&mut rand::rng(), 64)
.try_into()
.expect("generated string should be within size range for a session key")
}

View File

@ -48,7 +48,7 @@ async fn cookie_storage() -> std::io::Result<()> {
let deletion_cookie = logout_response.response().cookies().next().unwrap();
assert_eq!(deletion_cookie.name(), "id");
assert_eq!(deletion_cookie.path().unwrap(), "/test");
assert!(deletion_cookie.secure().is_none());
assert!(deletion_cookie.secure().unwrap());
assert!(deletion_cookie.http_only().unwrap());
assert_eq!(deletion_cookie.max_age().unwrap(), Duration::ZERO);
assert_eq!(deletion_cookie.domain().unwrap(), "localhost");

View File

@ -1,13 +1,14 @@
use std::collections::HashMap;
use std::convert::TryInto;
use actix_session::storage::{LoadError, SaveError, SessionKey, SessionStore, UpdateError};
use actix_session::{Session, SessionMiddleware};
use actix_web::body::MessageBody;
use actix_web::http::StatusCode;
use actix_session::{
storage::{LoadError, SaveError, SessionKey, SessionStore, UpdateError},
Session, SessionMiddleware,
};
use actix_web::{
body::MessageBody,
cookie::{time::Duration, Key},
dev::Service,
http::StatusCode,
test, web, App, Responder,
};
use anyhow::Error;
@ -43,7 +44,6 @@ async fn errors_are_opaque() {
struct MockStore;
#[async_trait::async_trait(?Send)]
impl SessionStore for MockStore {
async fn load(
&self,
@ -68,15 +68,18 @@ impl SessionStore for MockStore {
_session_state: HashMap<String, String>,
_ttl: &Duration,
) -> Result<SessionKey, UpdateError> {
todo!()
#![allow(clippy::diverging_sub_expression)]
unimplemented!()
}
async fn update_ttl(&self, _session_key: &SessionKey, _ttl: &Duration) -> Result<(), Error> {
todo!()
#![allow(clippy::diverging_sub_expression)]
unimplemented!()
}
async fn delete(&self, _session_key: &SessionKey) -> Result<(), Error> {
todo!()
#![allow(clippy::diverging_sub_expression)]
unimplemented!()
}
}

View File

@ -68,3 +68,84 @@ async fn session_entries() {
map.contains_key("test_str");
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();
session.insert("test_val", "val").unwrap();
assert_eq!(session.status(), SessionStatus::Changed);
session.renew();
assert_eq!(session.status(), SessionStatus::Renewed);
session.insert("test_val1", "val1").unwrap();
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();
session.insert("test_val", "val").unwrap();
session.remove("test_val").unwrap();
assert_eq!(session.status(), SessionStatus::Changed);
session.renew();
session.insert("test_val", "val").unwrap();
session.remove("test_val").unwrap();
assert_eq!(session.status(), SessionStatus::Renewed);
}
#[actix_web::test]
async fn clear_session_after_renew() {
let session = test::TestRequest::default().to_srv_request().get_session();
session.clear();
assert_eq!(session.status(), SessionStatus::Changed);
session.renew();
assert_eq!(session.status(), SessionStatus::Renewed);
session.clear();
assert_eq!(session.status(), SessionStatus::Renewed);
}

40
actix-settings/CHANGES.md Normal file
View File

@ -0,0 +1,40 @@
# Changes
## Unreleased
## 0.8.0
- Add `openssl` crate feature for TLS settings using OpenSSL.
- Add `ApplySettings::try_apply_settings()`.
- Implement TLS logic for `ApplySettings::try_apply_settings()`.
- Add `Tls::get_ssl_acceptor_builder()` function to build `openssl::ssl::SslAcceptorBuilder`.
- Deprecate `ApplySettings::apply_settings()`.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.7.1
- Fix doc examples.
## 0.7.0
- The `ApplySettings` trait now includes a type parameter, allowing multiple types to be implemented per configuration target.
- Implement `ApplySettings` for `ActixSettings`.
- `BasicSettings::from_default_template()` is now infallible.
- Rename `AtError => Error`.
- Remove `AtResult` type alias.
- Update `toml` dependency to `0.8`.
- Remove `ioe` dependency; `std::io::Error` is now used directly.
- Remove `Clone` implementation for `Error`.
- Implement `Display` for `Error`.
- Implement std's `Error` for `Error`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.6.0
- Update Actix Web dependencies to v4 ecosystem.
- Rename `actix.ssl` settings object to `actix.tls`.
- `NoSettings` is now marked `#[non_exhaustive]`.
## 0.5.2
- Adopted into @actix org from <https://github.com/jjpe/actix-settings>.

37
actix-settings/Cargo.toml Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "actix-settings"
version = "0.8.0"
authors = [
"Joey Ezechiels <joey.ezechiels@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Easily manage Actix Web's settings from a TOML file and environment variables"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
[features]
openssl = ["dep:openssl", "actix-web/openssl"]
[dependencies]
actix-http = "3"
actix-service = "2"
actix-web = { version = "4", default-features = false }
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"] }
toml = "0.8"
[dev-dependencies]
actix-web = "4"
env_logger = "0.11"
[lints]
workspace = true

31
actix-settings/README.md Normal file
View File

@ -0,0 +1,31 @@
# actix-settings
> Easily manage Actix Web's settings from a TOML file and environment variables.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-settings?label=latest)](https://crates.io/crates/actix-settings)
[![Documentation](https://docs.rs/actix-settings/badge.svg?version=0.8.0)](https://docs.rs/actix-settings/0.8.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-settings)
[![Dependency Status](https://deps.rs/crate/actix-settings/0.8.0/status.svg)](https://deps.rs/crate/actix-settings/0.8.0)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-settings)
- [Usage Example][usage]
- Minimum Supported Rust Version (MSRV): 1.57
### Custom Settings
There is a way to extend the available settings. This can be used to combine the settings provided by Actix Web and those provided by application server built using `actix`.
Have a look at [the usage example][usage] to see how.
## Special Thanks
This crate was made possible by support from Accept B.V and [@jjpe].
[usage]: https://github.com/actix/actix-extras/blob/master/actix-settings/examples/actix.rs
[@jjpe]: https://github.com/jjpe

View File

@ -0,0 +1,82 @@
use actix_settings::{ApplySettings as _, Mode, Settings};
use actix_web::{
get,
middleware::{Compress, Condition, Logger},
web, App, HttpServer, Responder,
};
#[get("/")]
async fn index(settings: web::Data<Settings>) -> impl Responder {
format!(
r#"{{
"mode": "{}",
"hosts": ["{}"]
}}"#,
match settings.actix.mode {
Mode::Development => "development",
Mode::Production => "production",
},
settings
.actix
.hosts
.iter()
.map(|addr| { format!("{}:{}", addr.host, addr.port) })
.collect::<Vec<_>>()
.join(", "),
)
.customize()
.insert_header(("content-type", "application/json"))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut settings = Settings::parse_toml("./examples/config.toml")
.expect("Failed to parse `Settings` from config.toml");
// If the environment variable `$APPLICATION__HOSTS` is set,
// have its value override the `settings.actix.hosts` setting:
Settings::override_field_with_env_var(&mut settings.actix.hosts, "APPLICATION__HOSTS")?;
init_logger(&settings);
HttpServer::new({
// clone settings into each worker thread
let settings = settings.clone();
move || {
App::new()
// Include this `.wrap()` call for compression settings to take effect:
.wrap(Condition::new(
settings.actix.enable_compression,
Compress::default(),
))
.wrap(Logger::default())
// make `Settings` available to handlers
.app_data(web::Data::new(settings.clone()))
.service(index)
}
})
// apply the `Settings` to Actix Web's `HttpServer`
.try_apply_settings(&settings)?
.run()
.await
}
/// Initialize the logging infrastructure.
fn init_logger(settings: &Settings) {
if !settings.actix.enable_log {
return;
}
std::env::set_var(
"RUST_LOG",
match settings.actix.mode {
Mode::Development => "actix_web=debug",
Mode::Production => "actix_web=info",
},
);
std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
}

View File

@ -0,0 +1,72 @@
[actix]
# For more info, see: https://docs.rs/actix-web/4/actix_web/struct.HttpServer.html.
hosts = [
["0.0.0.0", 8080] # This should work for both development and deployment...
# # ... but other entries are possible, as well.
]
mode = "development" # Either "development" or "production".
enable-compression = true # Toggle compression middleware.
enable-log = true # Toggle logging middleware.
# The number of workers that the server should start.
# By default the number of available logical cpu cores is used.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
num-workers = "default"
# The maximum number of pending connections. This refers to the number of clients
# that can be waiting to be served. Exceeding this number results in the client
# getting an error when attempting to connect. It should only affect servers under
# significant load. Generally set in the 64-2048 range. The default value is 2048.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
backlog = "default"
# Sets the per-worker maximum number of concurrent connections. All socket listeners
# will stop accepting connections when this limit is reached for each worker.
# By default max connections is set to a 25k.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
max-connections = "default"
# Sets the per-worker maximum concurrent connection establish process. All listeners
# will stop accepting connections when this limit is reached. It can be used to limit
# the global TLS CPU usage. By default max connections is set to a 256.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
max-connection-rate = "default"
# Set server keep-alive preference. By default keep alive is set to 5 seconds.
# Takes a string value: Either "default", "disabled", "os",
# or a string of the format "N seconds" where N is an integer > 0 e.g. "6 seconds".
keep-alive = "default"
# Set server client timeout in milliseconds for first request. Defines a timeout
# for reading client request header. If a client does not transmit the entire set of
# headers within this time, the request is terminated with the 408 (Request Time-out)
# error. To disable timeout, set the value to 0.
# By default client timeout is set to 5000 milliseconds.
# Takes a string value: Either "default", or a string of the format "N milliseconds"
# where N is an integer > 0 e.g. "6 milliseconds".
client-timeout = "default"
# Set server connection shutdown timeout in milliseconds. Defines a timeout for
# shutdown connection. If a shutdown procedure does not complete within this time,
# the request is dropped. To disable timeout set value to 0.
# By default client timeout is set to 5000 milliseconds.
# Takes a string value: Either "default", or a string of the format "N milliseconds"
# where N is an integer > 0 e.g. "6 milliseconds".
client-shutdown = "default"
# Timeout for graceful workers shutdown. After receiving a stop signal, workers have
# this much time to finish serving requests. Workers still alive after the timeout
# are force dropped. By default shutdown timeout sets to 30 seconds.
# Takes a string value: Either "default", or a string of the format "N seconds"
# where N is an integer > 0 e.g. "6 seconds".
shutdown-timeout = "default"
[actix.tls] # TLS is disabled by default because the certs don't exist
enabled = false
certificate = "path/to/cert/cert.pem"
private-key = "path/to/cert/key.pem"
# The `application` table be used to express application-specific settings.
# See the `README.md` file for more details on how to use this.
[application]

View File

@ -0,0 +1,72 @@
[actix]
# For more info, see: https://docs.rs/actix-web/4/actix_web/struct.HttpServer.html.
hosts = [
["0.0.0.0", 9000] # This should work for both development and deployment...
# # ... but other entries are possible, as well.
]
mode = "development" # Either "development" or "production".
enable-compression = true # Toggle compression middleware.
enable-log = true # Toggle logging middleware.
# The number of workers that the server should start.
# By default the number of available logical cpu cores is used.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
num-workers = "default"
# The maximum number of pending connections. This refers to the number of clients
# that can be waiting to be served. Exceeding this number results in the client
# getting an error when attempting to connect. It should only affect servers under
# significant load. Generally set in the 64-2048 range. The default value is 2048.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
backlog = "default"
# Sets the per-worker maximum number of concurrent connections. All socket listeners
# will stop accepting connections when this limit is reached for each worker.
# By default max connections is set to a 25k.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
max-connections = "default"
# Sets the per-worker maximum concurrent connection establish process. All listeners
# will stop accepting connections when this limit is reached. It can be used to limit
# the global TLS CPU usage. By default max connections is set to a 256.
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
max-connection-rate = "default"
# Set server keep-alive preference. By default keep alive is set to 5 seconds.
# Takes a string value: Either "default", "disabled", "os",
# or a string of the format "N seconds" where N is an integer > 0 e.g. "6 seconds".
keep-alive = "default"
# Set server client timeout in milliseconds for first request. Defines a timeout
# for reading client request header. If a client does not transmit the entire set of
# headers within this time, the request is terminated with the 408 (Request Time-out)
# error. To disable timeout, set the value to 0.
# By default client timeout is set to 5000 milliseconds.
# Takes a string value: Either "default", or a string of the format "N milliseconds"
# where N is an integer > 0 e.g. "6 milliseconds".
client-timeout = "default"
# Set server connection shutdown timeout in milliseconds. Defines a timeout for
# shutdown connection. If a shutdown procedure does not complete within this time,
# the request is dropped. To disable timeout set value to 0.
# By default client timeout is set to 5000 milliseconds.
# Takes a string value: Either "default", or a string of the format "N milliseconds"
# where N is an integer > 0 e.g. "6 milliseconds".
client-shutdown = "default"
# Timeout for graceful workers shutdown. After receiving a stop signal, workers have
# this much time to finish serving requests. Workers still alive after the timeout
# are force dropped. By default shutdown timeout sets to 30 seconds.
# Takes a string value: Either "default", or a string of the format "N seconds"
# where N is an integer > 0 e.g. "6 seconds".
shutdown-timeout = "default"
[actix.tls] # TLS is disabled by default because the certs don't exist
enabled = false
certificate = "path/to/cert/cert.pem"
private-key = "path/to/cert/key.pem"
# The `application` table be used to express application-specific settings.
# See the `README.md` file for more details on how to use this.
[application]

134
actix-settings/src/error.rs Normal file
View File

@ -0,0 +1,134 @@
use std::{env::VarError, io, num::ParseIntError, path::PathBuf, str::ParseBoolError};
use derive_more::derive::{Display, Error};
#[cfg(feature = "openssl")]
use openssl::error::ErrorStack as OpenSSLError;
use toml::de::Error as TomlError;
/// Errors that can be returned from methods in this crate.
#[derive(Debug, Display, Error)]
pub enum Error {
/// Environment variable does not exists or is invalid.
#[display("Env var error: {_0}")]
EnvVarError(VarError),
/// File already exists on disk.
#[display("File exists: {}", _0.display())]
FileExists(#[error(not(source))] PathBuf),
/// Invalid value.
#[allow(missing_docs)]
#[display("Expected {expected}, got {got} (@ {file}:{line}:{column})")]
InvalidValue {
expected: &'static str,
got: String,
file: &'static str,
line: u32,
column: u32,
},
/// I/O error.
#[display("I/O error: {_0}")]
IoError(io::Error),
/// OpenSSL Error.
#[cfg(feature = "openssl")]
#[display("OpenSSL error: {_0}")]
OpenSSLError(OpenSSLError),
/// Value is not a boolean.
#[display("Failed to parse boolean: {_0}")]
ParseBoolError(ParseBoolError),
/// Value is not an integer.
#[display("Failed to parse integer: {_0}")]
ParseIntError(ParseIntError),
/// Value is not an address.
#[display("Failed to parse address: {_0}")]
ParseAddressError(#[error(not(source))] String),
/// Error deserializing as TOML.
#[display("TOML error: {_0}")]
TomlError(TomlError),
}
macro_rules! InvalidValue {
(expected: $expected:expr, got: $got:expr,) => {
crate::Error::InvalidValue {
expected: $expected,
got: $got.to_string(),
file: file!(),
line: line!(),
column: column!(),
}
};
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Self::IoError(err)
}
}
#[cfg(feature = "openssl")]
impl From<OpenSSLError> for Error {
fn from(err: OpenSSLError) -> Self {
Self::OpenSSLError(err)
}
}
impl From<ParseBoolError> for Error {
fn from(err: ParseBoolError) -> Self {
Self::ParseBoolError(err)
}
}
impl From<ParseIntError> for Error {
fn from(err: ParseIntError) -> Self {
Self::ParseIntError(err)
}
}
impl From<TomlError> for Error {
fn from(err: TomlError) -> Self {
Self::TomlError(err)
}
}
impl From<VarError> for Error {
fn from(err: VarError) -> Self {
Self::EnvVarError(err)
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
match err {
Error::EnvVarError(_) => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()),
Error::FileExists(_) => io::Error::new(io::ErrorKind::AlreadyExists, err.to_string()),
Error::InvalidValue { .. } => {
io::Error::new(io::ErrorKind::InvalidInput, err.to_string())
}
Error::IoError(io_error) => io_error,
#[cfg(feature = "openssl")]
Error::OpenSSLError(ossl_error) => io::Error::new(io::ErrorKind::Other, ossl_error),
Error::ParseBoolError(_) => {
io::Error::new(io::ErrorKind::InvalidInput, err.to_string())
}
Error::ParseIntError(_) => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()),
Error::ParseAddressError(_) => {
io::Error::new(io::ErrorKind::InvalidInput, err.to_string())
}
Error::TomlError(_) => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()),
}
}
}

860
actix-settings/src/lib.rs Normal file
View File

@ -0,0 +1,860 @@
//! Easily manage Actix Web's settings from a TOML file and environment variables.
//!
//! To get started add a [`Settings::parse_toml("./Server.toml")`](Settings::parse_toml) call to the
//! top of your main function. This will create a template file with descriptions of all the
//! configurable settings. You can change or remove anything in that file and it will be picked up
//! the next time you run your application.
//!
//! Overriding parts of the file can be done from values using [`Settings::override_field`] or from
//! the environment using [`Settings::override_field_with_env_var`].
//!
//! # Examples
//!
//! See examples folder on GitHub for complete example.
//!
//! ```ignore
//! # use actix_web::{
//! # get,
//! # middleware::{Compress, Condition, Logger},
//! # web, App, HttpServer,
//! # };
//! use actix_settings::{ApplySettings as _, Mode, Settings};
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! let mut settings = Settings::parse_toml("./Server.toml")
//! .expect("Failed to parse `Settings` from Server.toml");
//!
//! // If the environment variable `$APPLICATION__HOSTS` is set,
//! // have its value override the `settings.actix.hosts` setting:
//! Settings::override_field_with_env_var(&mut settings.actix.hosts, "APPLICATION__HOSTS")?;
//!
//! init_logger(&settings);
//!
//! HttpServer::new({
//! // clone settings into each worker thread
//! let settings = settings.clone();
//!
//! move || {
//! App::new()
//! // Include this `.wrap()` call for compression settings to take effect
//! .wrap(Condition::new(
//! settings.actix.enable_compression,
//! Compress::default(),
//! ))
//!
//! // add request logger
//! .wrap(Logger::default())
//!
//! // make `Settings` available to handlers
//! .app_data(web::Data::new(settings.clone()))
//!
//! // add request handlers as normal
//! .service(index)
//! }
//! })
//! // apply the `Settings` to Actix Web's `HttpServer`
//! .try_apply_settings(&settings)?
//! .run()
//! .await
//! }
//! ```
#![forbid(unsafe_code)]
#![warn(missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{
env, fmt,
fs::File,
io::{Read as _, Write as _},
path::Path,
time::Duration,
};
use actix_http::{Request, Response};
use actix_service::IntoServiceFactory;
use actix_web::{
body::MessageBody,
dev::{AppConfig, ServiceFactory},
http::KeepAlive as ActixKeepAlive,
Error as WebError, HttpServer,
};
use serde::{de, Deserialize};
#[macro_use]
mod error;
mod parse;
mod settings;
#[cfg(feature = "openssl")]
pub use self::settings::Tls;
pub use self::{
error::Error,
parse::Parse,
settings::{
ActixSettings, Address, Backlog, KeepAlive, MaxConnectionRate, MaxConnections, Mode,
NumWorkers, Timeout,
},
};
/// Convenience type alias for `Result<T, AtError>`.
type AsResult<T> = std::result::Result<T, Error>;
/// Wrapper for server and application-specific settings.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(bound = "A: Deserialize<'de>")]
pub struct BasicSettings<A> {
/// Actix Web server settings.
pub actix: ActixSettings,
/// Application-specific settings.
pub application: A,
}
/// Convenience type alias for [`BasicSettings`] with no defined application-specific settings.
pub type Settings = BasicSettings<NoSettings>;
/// Marker type representing no defined application-specific settings.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[non_exhaustive]
pub struct NoSettings {/* NOTE: turning this into a unit struct will cause deserialization failures. */}
impl<A> BasicSettings<A>
where
A: de::DeserializeOwned,
{
// NOTE **DO NOT** mess with the ordering of the tables in the default template.
// Especially the `[application]` table needs to be last in order
// for some tests to keep working.
/// Default settings file contents.
pub(crate) const DEFAULT_TOML_TEMPLATE: &'static str = include_str!("./defaults.toml");
/// Parse an instance of `Self` from a TOML file located at `filepath`.
///
/// If the file doesn't exist, it is generated from the default TOML template, after which the
/// newly generated file is read in and parsed.
pub fn parse_toml<P>(filepath: P) -> AsResult<Self>
where
P: AsRef<Path>,
{
let filepath = filepath.as_ref();
if !filepath.exists() {
Self::write_toml_file(filepath)?;
}
let mut f = File::open(filepath)?;
let len_guess = f.metadata().map(|md| md.len()).unwrap_or(128);
let mut contents = String::with_capacity(len_guess as usize);
f.read_to_string(&mut contents)?;
Ok(toml::from_str::<Self>(&contents)?)
}
/// Parse an instance of `Self` straight from the default TOML template.
pub fn from_default_template() -> Self {
Self::from_template(Self::DEFAULT_TOML_TEMPLATE).unwrap()
}
/// Parse an instance of `Self` straight from the default TOML template.
pub fn from_template(template: &str) -> AsResult<Self> {
Ok(toml::from_str(template)?)
}
/// Writes the default TOML template to a new file, located at `filepath`.
///
/// # Errors
///
/// Returns a [`FileExists`](crate::Error::FileExists) error if a file already exists at that
/// location.
pub fn write_toml_file<P>(filepath: P) -> AsResult<()>
where
P: AsRef<Path>,
{
let filepath = filepath.as_ref();
if filepath.exists() {
return Err(Error::FileExists(filepath.to_path_buf()));
}
let mut file = File::create(filepath)?;
file.write_all(Self::DEFAULT_TOML_TEMPLATE.trim().as_bytes())?;
file.flush()?;
Ok(())
}
/// Attempts to parse `value` and override the referenced `field`.
///
/// # Examples
/// ```
/// use actix_settings::{Settings, Mode};
///
/// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template();
/// assert_eq!(settings.actix.mode, Mode::Development);
///
/// Settings::override_field(&mut settings.actix.mode, "production")?;
/// assert_eq!(settings.actix.mode, Mode::Production);
/// # Ok(()) }
/// ```
pub fn override_field<F, V>(field: &mut F, value: V) -> AsResult<()>
where
F: Parse,
V: AsRef<str>,
{
*field = F::parse(value.as_ref())?;
Ok(())
}
/// Attempts to read an environment variable, parse it, and override the referenced `field`.
///
/// # Examples
/// ```
/// use actix_settings::{Settings, Mode};
///
/// std::env::set_var("OVERRIDE__MODE", "production");
///
/// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template();
/// assert_eq!(settings.actix.mode, Mode::Development);
///
/// Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE")?;
/// assert_eq!(settings.actix.mode, Mode::Production);
/// # Ok(()) }
/// ```
pub fn override_field_with_env_var<F, N>(field: &mut F, var_name: N) -> AsResult<()>
where
F: Parse,
N: AsRef<str>,
{
match env::var(var_name.as_ref()) {
Err(env::VarError::NotPresent) => Ok((/*NOP*/)),
Err(var_error) => Err(Error::from(var_error)),
Ok(value) => Self::override_field(field, value),
}
}
}
/// Extension trait for applying parsed settings to the server object.
pub trait ApplySettings<S>: Sized {
/// Applies some settings object value to `self`.
///
/// The default implementation calls [`try_apply_settings()`].
///
/// # Panics
///
/// May panic if settings are invalid or cannot be applied.
///
/// [`try_apply_settings()`]: ApplySettings::try_apply_settings().
#[deprecated = "Prefer `try_apply_settings()`."]
fn apply_settings(self, settings: &S) -> Self {
self.try_apply_settings(settings)
.expect("Could not apply settings")
}
/// Applies some settings object value to `self`.
///
/// # Errors
///
/// May return error if settings are invalid or cannot be applied.
fn try_apply_settings(self, settings: &S) -> AsResult<Self>;
}
impl<F, I, S, B> ApplySettings<ActixSettings> for HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<WebError> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
S::Future: 'static,
B: MessageBody + 'static,
{
fn apply_settings(self, settings: &ActixSettings) -> Self {
self.try_apply_settings(settings).unwrap()
}
fn try_apply_settings(mut self, settings: &ActixSettings) -> AsResult<Self> {
for Address { host, port } in &settings.hosts {
#[cfg(feature = "openssl")]
{
if settings.tls.enabled {
self = self.bind_openssl(
format!("{}:{}", host, port),
settings.tls.get_ssl_acceptor_builder()?,
)?;
} else {
self = self.bind(format!("{host}:{port}"))?;
}
}
#[cfg(not(feature = "openssl"))]
{
self = self.bind(format!("{host}:{port}"))?;
}
}
self = match settings.num_workers {
NumWorkers::Default => self,
NumWorkers::Manual(n) => self.workers(n),
};
self = match settings.backlog {
Backlog::Default => self,
Backlog::Manual(n) => self.backlog(n as u32),
};
self = match settings.max_connections {
MaxConnections::Default => self,
MaxConnections::Manual(n) => self.max_connections(n),
};
self = match settings.max_connection_rate {
MaxConnectionRate::Default => self,
MaxConnectionRate::Manual(n) => self.max_connection_rate(n),
};
self = match settings.keep_alive {
KeepAlive::Default => self,
KeepAlive::Disabled => self.keep_alive(ActixKeepAlive::Disabled),
KeepAlive::Os => self.keep_alive(ActixKeepAlive::Os),
KeepAlive::Seconds(n) => self.keep_alive(Duration::from_secs(n as u64)),
};
self = match settings.client_timeout {
Timeout::Default => self,
Timeout::Milliseconds(n) => {
self.client_request_timeout(Duration::from_millis(n as u64))
}
Timeout::Seconds(n) => self.client_request_timeout(Duration::from_secs(n as u64)),
};
self = match settings.client_shutdown {
Timeout::Default => self,
Timeout::Milliseconds(n) => {
self.client_disconnect_timeout(Duration::from_millis(n as u64))
}
Timeout::Seconds(n) => self.client_disconnect_timeout(Duration::from_secs(n as u64)),
};
self = match settings.shutdown_timeout {
Timeout::Default => self,
Timeout::Milliseconds(_) => self.shutdown_timeout(1),
Timeout::Seconds(n) => self.shutdown_timeout(n as u64),
};
Ok(self)
}
}
impl<F, I, S, B, A> ApplySettings<BasicSettings<A>> for HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<WebError> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
S::Future: 'static,
B: MessageBody + 'static,
A: de::DeserializeOwned,
{
fn apply_settings(self, settings: &BasicSettings<A>) -> Self {
self.try_apply_settings(&settings.actix).unwrap()
}
fn try_apply_settings(self, settings: &BasicSettings<A>) -> AsResult<Self> {
self.try_apply_settings(&settings.actix)
}
}
#[cfg(test)]
mod tests {
use actix_web::App;
use super::*;
#[test]
fn apply_settings() {
let settings = Settings::parse_toml("Server.toml").unwrap();
let server = HttpServer::new(App::new).try_apply_settings(&settings);
assert!(server.is_ok());
}
#[test]
fn override_field_hosts() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.hosts,
vec![Address {
host: "0.0.0.0".into(),
port: 9000
},]
);
Settings::override_field(
&mut settings.actix.hosts,
r#"[
["0.0.0.0", 1234],
["localhost", 2345]
]"#,
)
.unwrap();
assert_eq!(
settings.actix.hosts,
vec![
Address {
host: "0.0.0.0".into(),
port: 1234
},
Address {
host: "localhost".into(),
port: 2345
},
]
);
}
#[test]
fn override_field_with_env_var_hosts() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.hosts,
vec![Address {
host: "0.0.0.0".into(),
port: 9000
},]
);
std::env::set_var(
"OVERRIDE__HOSTS",
r#"[
["0.0.0.0", 1234],
["localhost", 2345]
]"#,
);
Settings::override_field_with_env_var(&mut settings.actix.hosts, "OVERRIDE__HOSTS")
.unwrap();
assert_eq!(
settings.actix.hosts,
vec![
Address {
host: "0.0.0.0".into(),
port: 1234
},
Address {
host: "localhost".into(),
port: 2345
},
]
);
}
#[test]
fn override_field_mode() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.mode, Mode::Development);
Settings::override_field(&mut settings.actix.mode, "production").unwrap();
assert_eq!(settings.actix.mode, Mode::Production);
}
#[test]
fn override_field_with_env_var_mode() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.mode, Mode::Development);
std::env::set_var("OVERRIDE__MODE", "production");
Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE").unwrap();
assert_eq!(settings.actix.mode, Mode::Production);
}
#[test]
fn override_field_enable_compression() {
let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_compression);
Settings::override_field(&mut settings.actix.enable_compression, "false").unwrap();
assert!(!settings.actix.enable_compression);
}
#[test]
fn override_field_with_env_var_enable_compression() {
let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_compression);
std::env::set_var("OVERRIDE__ENABLE_COMPRESSION", "false");
Settings::override_field_with_env_var(
&mut settings.actix.enable_compression,
"OVERRIDE__ENABLE_COMPRESSION",
)
.unwrap();
assert!(!settings.actix.enable_compression);
}
#[test]
fn override_field_enable_log() {
let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_log);
Settings::override_field(&mut settings.actix.enable_log, "false").unwrap();
assert!(!settings.actix.enable_log);
}
#[test]
fn override_field_with_env_var_enable_log() {
let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_log);
std::env::set_var("OVERRIDE__ENABLE_LOG", "false");
Settings::override_field_with_env_var(
&mut settings.actix.enable_log,
"OVERRIDE__ENABLE_LOG",
)
.unwrap();
assert!(!settings.actix.enable_log);
}
#[test]
fn override_field_num_workers() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.num_workers, NumWorkers::Default);
Settings::override_field(&mut settings.actix.num_workers, "42").unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42));
}
#[test]
fn override_field_with_env_var_num_workers() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.num_workers, NumWorkers::Default);
std::env::set_var("OVERRIDE__NUM_WORKERS", "42");
Settings::override_field_with_env_var(
&mut settings.actix.num_workers,
"OVERRIDE__NUM_WORKERS",
)
.unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42));
}
#[test]
fn override_field_backlog() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.backlog, Backlog::Default);
Settings::override_field(&mut settings.actix.backlog, "42").unwrap();
assert_eq!(settings.actix.backlog, Backlog::Manual(42));
}
#[test]
fn override_field_with_env_var_backlog() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.backlog, Backlog::Default);
std::env::set_var("OVERRIDE__BACKLOG", "42");
Settings::override_field_with_env_var(&mut settings.actix.backlog, "OVERRIDE__BACKLOG")
.unwrap();
assert_eq!(settings.actix.backlog, Backlog::Manual(42));
}
#[test]
fn override_field_max_connections() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.max_connections, MaxConnections::Default);
Settings::override_field(&mut settings.actix.max_connections, "42").unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42));
}
#[test]
fn override_field_with_env_var_max_connections() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.max_connections, MaxConnections::Default);
std::env::set_var("OVERRIDE__MAX_CONNECTIONS", "42");
Settings::override_field_with_env_var(
&mut settings.actix.max_connections,
"OVERRIDE__MAX_CONNECTIONS",
)
.unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42));
}
#[test]
fn override_field_max_connection_rate() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Default
);
Settings::override_field(&mut settings.actix.max_connection_rate, "42").unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Manual(42)
);
}
#[test]
fn override_field_with_env_var_max_connection_rate() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Default
);
std::env::set_var("OVERRIDE__MAX_CONNECTION_RATE", "42");
Settings::override_field_with_env_var(
&mut settings.actix.max_connection_rate,
"OVERRIDE__MAX_CONNECTION_RATE",
)
.unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Manual(42)
);
}
#[test]
fn override_field_keep_alive() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
Settings::override_field(&mut settings.actix.keep_alive, "42 seconds").unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42));
}
#[test]
fn override_field_with_env_var_keep_alive() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
std::env::set_var("OVERRIDE__KEEP_ALIVE", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.keep_alive,
"OVERRIDE__KEEP_ALIVE",
)
.unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42));
}
#[test]
fn override_field_client_timeout() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.client_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_client_timeout() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.client_timeout,
"OVERRIDE__CLIENT_TIMEOUT",
)
.unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_client_shutdown() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_shutdown, Timeout::Default);
Settings::override_field(&mut settings.actix.client_shutdown, "42 seconds").unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_client_shutdown() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_shutdown, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_SHUTDOWN", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.client_shutdown,
"OVERRIDE__CLIENT_SHUTDOWN",
)
.unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42));
}
#[test]
fn override_field_shutdown_timeout() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.shutdown_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_shutdown_timeout() {
let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__SHUTDOWN_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.shutdown_timeout,
"OVERRIDE__SHUTDOWN_TIMEOUT",
)
.unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_enabled() {
let mut settings = Settings::from_default_template();
assert!(!settings.actix.tls.enabled);
Settings::override_field(&mut settings.actix.tls.enabled, "true").unwrap();
assert!(settings.actix.tls.enabled);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_enabled() {
let mut settings = Settings::from_default_template();
assert!(!settings.actix.tls.enabled);
std::env::set_var("OVERRIDE__TLS_ENABLED", "true");
Settings::override_field_with_env_var(
&mut settings.actix.tls.enabled,
"OVERRIDE__TLS_ENABLED",
)
.unwrap();
assert!(settings.actix.tls.enabled);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_certificate() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem")
);
Settings::override_field(
&mut settings.actix.tls.certificate,
"/overridden/path/to/cert/cert.pem",
)
.unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("/overridden/path/to/cert/cert.pem")
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_certificate() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem")
);
std::env::set_var(
"OVERRIDE__TLS_CERTIFICATE",
"/overridden/path/to/cert/cert.pem",
);
Settings::override_field_with_env_var(
&mut settings.actix.tls.certificate,
"OVERRIDE__TLS_CERTIFICATE",
)
.unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("/overridden/path/to/cert/cert.pem")
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_private_key() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem")
);
Settings::override_field(
&mut settings.actix.tls.private_key,
"/overridden/path/to/cert/key.pem",
)
.unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("/overridden/path/to/cert/key.pem")
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_private_key() {
let mut settings = Settings::from_default_template();
assert_eq!(
settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem")
);
std::env::set_var(
"OVERRIDE__TLS_PRIVATE_KEY",
"/overridden/path/to/cert/key.pem",
);
Settings::override_field_with_env_var(
&mut settings.actix.tls.private_key,
"OVERRIDE__TLS_PRIVATE_KEY",
)
.unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("/overridden/path/to/cert/key.pem")
);
}
#[test]
fn override_extended_field_with_custom_type() {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct NestedSetting {
foo: String,
bar: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct AppSettings {
example_name: String,
nested_field: NestedSetting,
}
type CustomSettings = BasicSettings<AppSettings>;
let mut settings = CustomSettings::from_template(
&(CustomSettings::DEFAULT_TOML_TEMPLATE.to_string()
// NOTE: Add these entries to the `[application]` table:
+ "\nexample-name = \"example value\""
+ "\nnested-field = { foo = \"foo\", bar = false }"),
)
.unwrap();
assert_eq!(
settings.application,
AppSettings {
example_name: "example value".into(),
nested_field: NestedSetting {
foo: "foo".into(),
bar: false,
},
}
);
CustomSettings::override_field(
&mut settings.application.example_name,
"/overridden/path/to/cert/key.pem",
)
.unwrap();
assert_eq!(
settings.application,
AppSettings {
example_name: "/overridden/path/to/cert/key.pem".into(),
nested_field: NestedSetting {
foo: "foo".into(),
bar: false,
},
}
);
}
}

View File

@ -0,0 +1,40 @@
use std::{path::PathBuf, str::FromStr};
use crate::Error;
/// A specialized `FromStr` trait that returns [`Error`] errors
pub trait Parse: Sized {
/// Parse `Self` from `string`.
fn parse(string: &str) -> Result<Self, Error>;
}
impl Parse for bool {
fn parse(string: &str) -> Result<Self, Error> {
Self::from_str(string).map_err(Error::from)
}
}
macro_rules! impl_parse_for_int_type {
($($int_type:ty),+ $(,)?) => {
$(
impl Parse for $int_type {
fn parse(string: &str) -> Result<Self, Error> {
Self::from_str(string).map_err(Error::from)
}
}
)+
}
}
impl_parse_for_int_type![i8, i16, i32, i64, i128, u8, u16, u32, u64, u128];
impl Parse for String {
fn parse(string: &str) -> Result<Self, Error> {
Ok(string.to_string())
}
}
impl Parse for PathBuf {
fn parse(string: &str) -> Result<Self, Error> {
Ok(PathBuf::from(string))
}
}

View File

@ -0,0 +1,93 @@
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use crate::{Error, Parse};
static ADDR_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r#"(?x)
\[ # opening square bracket
(\s)* # optional whitespace
"(?P<host>[^"]+)" # host name (string)
, # separating comma
(\s)* # optional whitespace
(?P<port>\d+) # port number (integer)
(\s)* # optional whitespace
\] # closing square bracket
"#,
)
.expect("Failed to compile regex: ADDR_REGEX")
});
static ADDR_LIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r#"(?x)
\[ # opening square bracket (list)
(\s)* # optional whitespace
(?P<elements>(
\[".*", (\s)* \d+\] # element
(,)? # element separator
(\s)* # optional whitespace
)*)
(\s)* # optional whitespace
\] # closing square bracket (list)
"#,
)
.expect("Failed to compile regex: ADDRS_REGEX")
});
/// A host/port pair for the server to bind to.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
pub struct Address {
/// Host part of address.
pub host: String,
/// Port part of address.
pub port: u16,
}
impl Parse for Address {
fn parse(string: &str) -> Result<Self, Error> {
let mut items = string
.trim()
.trim_start_matches('[')
.trim_end_matches(']')
.split(',');
let parse_error = || Error::ParseAddressError(string.to_string());
if !ADDR_REGEX.is_match(string) {
return Err(parse_error());
}
Ok(Self {
host: items.next().ok_or_else(parse_error)?.trim().to_string(),
port: items.next().ok_or_else(parse_error)?.trim().parse()?,
})
}
}
impl Parse for Vec<Address> {
fn parse(string: &str) -> Result<Self, Error> {
let parse_error = || Error::ParseAddressError(string.to_string());
if !ADDR_LIST_REGEX.is_match(string) {
return Err(parse_error());
}
let mut addrs = vec![];
for list_caps in ADDR_LIST_REGEX.captures_iter(string) {
let elements = &list_caps["elements"].trim();
for elt_caps in ADDR_REGEX.captures_iter(elements) {
addrs.push(Address {
host: elt_caps["host"].to_string(),
port: elt_caps["port"].parse()?,
});
}
}
Ok(addrs)
}
}

View File

@ -0,0 +1,70 @@
use std::fmt;
use serde::de;
use crate::{AsResult, Error, Parse};
/// The maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served. Exceeding this number
/// results in the client getting an error when attempting to connect. It should only affect servers
/// under significant load.
///
/// Generally set in the 642048 range. The default value is 2048. Takes a string value: Either
/// "default", or an integer N > 0 e.g. "6".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Backlog {
/// The default number of connections. See struct docs.
Default,
/// A specific number of connections.
Manual(usize),
}
impl Parse for Backlog {
fn parse(string: &str) -> AsResult<Self> {
match string {
"default" => Ok(Backlog::Default),
string => match string.parse::<usize>() {
Ok(val) => Ok(Backlog::Manual(val)),
Err(_) => Err(InvalidValue! {
expected: "an integer > 0",
got: string,
}),
},
}
}
}
impl<'de> de::Deserialize<'de> for Backlog {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct BacklogVisitor;
impl de::Visitor<'_> for BacklogVisitor {
type Value = Backlog;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\" or a string containing an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match Backlog::parse(value) {
Ok(backlog) => Ok(backlog),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(BacklogVisitor)
}
}

View File

@ -0,0 +1,95 @@
use std::fmt;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::de;
use crate::{AsResult, Error, Parse};
/// The server keep-alive preference.
///
/// By default keep alive is set to 5 seconds. Takes a string value: Either "default", "disabled",
/// "os", or a string of the format "N seconds" where N is an integer > 0 e.g. "6 seconds".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum KeepAlive {
/// The default keep-alive as defined by Actix Web.
Default,
/// Disable keep-alive.
Disabled,
/// Let the OS determine keep-alive duration.
///
/// Note: this is usually quite long.
Os,
/// A specific keep-alive duration (in seconds).
Seconds(usize),
}
impl Parse for KeepAlive {
fn parse(string: &str) -> AsResult<Self> {
pub(crate) static FMT: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\d+ seconds$").expect("Failed to compile regex: FMT"));
pub(crate) static DIGITS: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\d+").expect("Failed to compile regex: FMT"));
macro_rules! invalid_value {
($got:expr) => {
Err(InvalidValue! {
expected: "a string of the format \"N seconds\" where N is an integer > 0",
got: $got,
})
};
}
let digits_in = |m: regex::Match<'_>| &string[m.start()..m.end()];
match string {
"default" => Ok(KeepAlive::Default),
"disabled" => Ok(KeepAlive::Disabled),
"OS" | "os" => Ok(KeepAlive::Os),
string if !FMT.is_match(string) => invalid_value!(string),
string => match DIGITS.find(string) {
None => invalid_value!(string),
Some(mat) => match digits_in(mat).parse() {
Ok(val) => Ok(KeepAlive::Seconds(val)),
Err(_) => invalid_value!(string),
},
},
}
}
}
impl<'de> de::Deserialize<'de> for KeepAlive {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct KeepAliveVisitor;
impl de::Visitor<'_> for KeepAliveVisitor {
type Value = KeepAlive;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\", \"disabled\", \"os\", or a string of the format \"N seconds\" where N is an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match KeepAlive::parse(value) {
Ok(keep_alive) => Ok(keep_alive),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(KeepAliveVisitor)
}
}

View File

@ -0,0 +1,67 @@
use std::fmt;
use serde::de;
use crate::{AsResult, Error, Parse};
/// The maximum per-worker concurrent TLS connection limit.
///
/// All listeners will stop accepting connections when this limit is reached. It can be used to
/// limit the global TLS CPU usage. By default max connections is set to a 256. Takes a string
/// value: Either "default", or an integer N > 0 e.g. "6".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MaxConnectionRate {
/// The default connection limit. See struct docs.
Default,
/// A specific connection limit.
Manual(usize),
}
impl Parse for MaxConnectionRate {
fn parse(string: &str) -> AsResult<Self> {
match string {
"default" => Ok(MaxConnectionRate::Default),
string => match string.parse::<usize>() {
Ok(val) => Ok(MaxConnectionRate::Manual(val)),
Err(_) => Err(InvalidValue! {
expected: "an integer > 0",
got: string,
}),
},
}
}
}
impl<'de> de::Deserialize<'de> for MaxConnectionRate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct MaxConnectionRateVisitor;
impl de::Visitor<'_> for MaxConnectionRateVisitor {
type Value = MaxConnectionRate;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\" or a string containing an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match MaxConnectionRate::parse(value) {
Ok(max_connection_rate) => Ok(max_connection_rate),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(MaxConnectionRateVisitor)
}
}

View File

@ -0,0 +1,67 @@
use std::fmt;
use serde::de;
use crate::{AsResult, Error, Parse};
/// The maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached for each worker.
/// By default max connections is set to a 25k. Takes a string value: Either "default", or an
/// integer N > 0 e.g. "6".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MaxConnections {
/// The default number of connections. See struct docs.
Default,
/// A specific number of connections.
Manual(usize),
}
impl Parse for MaxConnections {
fn parse(string: &str) -> AsResult<Self> {
match string {
"default" => Ok(MaxConnections::Default),
string => match string.parse::<usize>() {
Ok(val) => Ok(MaxConnections::Manual(val)),
Err(_) => Err(InvalidValue! {
expected: "an integer > 0",
got: string,
}),
},
}
}
}
impl<'de> de::Deserialize<'de> for MaxConnections {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct MaxConnectionsVisitor;
impl de::Visitor<'_> for MaxConnectionsVisitor {
type Value = MaxConnections;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\" or a string containing an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match MaxConnections::parse(value) {
Ok(max_connections) => Ok(max_connections),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(MaxConnectionsVisitor)
}
}

View File

@ -0,0 +1,65 @@
use serde::Deserialize;
mod address;
mod backlog;
mod keep_alive;
mod max_connection_rate;
mod max_connections;
mod mode;
mod num_workers;
mod timeout;
#[cfg(feature = "openssl")]
mod tls;
#[cfg(feature = "openssl")]
pub use self::tls::Tls;
pub use self::{
address::Address, backlog::Backlog, keep_alive::KeepAlive,
max_connection_rate::MaxConnectionRate, max_connections::MaxConnections, mode::Mode,
num_workers::NumWorkers, timeout::Timeout,
};
/// Settings types for Actix Web.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ActixSettings {
/// List of addresses for the server to bind to.
pub hosts: Vec<Address>,
/// Marker of intended deployment environment.
pub mode: Mode,
/// True if the `Compress` middleware should be enabled.
pub enable_compression: bool,
/// True if the [`Logger`](actix_web::middleware::Logger) middleware should be enabled.
pub enable_log: bool,
/// The number of workers that the server should start.
pub num_workers: NumWorkers,
/// The maximum number of pending connections.
pub backlog: Backlog,
/// The per-worker maximum number of concurrent connections.
pub max_connections: MaxConnections,
/// The per-worker maximum concurrent TLS connection limit.
pub max_connection_rate: MaxConnectionRate,
/// Server keep-alive preference.
pub keep_alive: KeepAlive,
/// Timeout duration for reading client request header.
pub client_timeout: Timeout,
/// Timeout duration for connection shutdown.
pub client_shutdown: Timeout,
/// Timeout duration for graceful worker shutdown.
pub shutdown_timeout: Timeout,
/// TLS (HTTPS) configuration.
#[cfg(feature = "openssl")]
pub tls: Tls,
}

View File

@ -0,0 +1,27 @@
use serde::Deserialize;
use crate::{AsResult, Parse};
/// Marker of intended deployment environment.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
/// Marks development environment.
Development,
/// Marks production environment.
Production,
}
impl Parse for Mode {
fn parse(string: &str) -> AsResult<Self> {
match string {
"development" => Ok(Self::Development),
"production" => Ok(Self::Production),
_ => Err(InvalidValue! {
expected: "\"development\" | \"production\".",
got: string,
}),
}
}
}

View File

@ -0,0 +1,66 @@
use std::fmt;
use serde::de;
use crate::{AsResult, Error, Parse};
/// The number of workers that the server should start.
///
/// By default the number of available logical cpu cores is used. Takes a string value: Either
/// "default", or an integer N > 0 e.g. "6".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NumWorkers {
/// The default number of workers. See struct docs.
Default,
/// A specific number of workers.
Manual(usize),
}
impl Parse for NumWorkers {
fn parse(string: &str) -> AsResult<Self> {
match string {
"default" => Ok(NumWorkers::Default),
string => match string.parse::<usize>() {
Ok(val) => Ok(NumWorkers::Manual(val)),
Err(_) => Err(InvalidValue! {
expected: "a positive integer",
got: string,
}),
},
}
}
}
impl<'de> de::Deserialize<'de> for NumWorkers {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct NumWorkersVisitor;
impl de::Visitor<'_> for NumWorkersVisitor {
type Value = NumWorkers;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\" or a string containing an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match NumWorkers::parse(value) {
Ok(num_workers) => Ok(num_workers),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(NumWorkersVisitor)
}
}

View File

@ -0,0 +1,98 @@
use std::fmt;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::de;
use crate::{AsResult, Error, Parse};
/// A timeout duration in milliseconds or seconds.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Timeout {
/// The default timeout. Depends on context.
Default,
/// Timeout in milliseconds.
Milliseconds(usize),
/// Timeout in seconds.
Seconds(usize),
}
impl Parse for Timeout {
fn parse(string: &str) -> AsResult<Self> {
pub static FMT: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^\d+ (milliseconds|seconds)$").expect("Failed to compile regex: FMT")
});
pub static DIGITS: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\d+").expect("Failed to compile regex: DIGITS"));
pub static UNIT: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(milliseconds|seconds)$").expect("Failed to compile regex: UNIT")
});
macro_rules! invalid_value {
($got:expr) => {
Err(InvalidValue! {
expected: "a string of the format \"N seconds\" or \"N milliseconds\" where N is an integer > 0",
got: $got,
})
}
}
match string {
"default" => Ok(Timeout::Default),
string if !FMT.is_match(string) => invalid_value!(string),
string => match (DIGITS.find(string), UNIT.find(string)) {
(None, _) | (_, None) => invalid_value!(string),
(Some(digits), Some(unit)) => {
let digits = &string[digits.range()];
let unit = &string[unit.range()];
match (digits.parse(), unit) {
(Ok(n), "milliseconds") => Ok(Timeout::Milliseconds(n)),
(Ok(n), "seconds") => Ok(Timeout::Seconds(n)),
_ => invalid_value!(string),
}
}
},
}
}
}
impl<'de> de::Deserialize<'de> for Timeout {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct TimeoutVisitor;
impl de::Visitor<'_> for TimeoutVisitor {
type Value = Timeout;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = "Either \"default\", \"disabled\", \"os\", or a string of the format \"N seconds\" where N is an integer > 0";
f.write_str(msg)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match Timeout::parse(value) {
Ok(num_workers) => Ok(num_workers),
Err(Error::InvalidValue { expected, got, .. }) => Err(
de::Error::invalid_value(de::Unexpected::Str(&got), &expected),
),
Err(_) => unreachable!(),
}
}
}
deserializer.deserialize_string(TimeoutVisitor)
}
}

View File

@ -0,0 +1,57 @@
use std::path::PathBuf;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
use serde::Deserialize;
use crate::AsResult;
/// TLS (HTTPS) configuration.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[doc(alias = "ssl", alias = "https")]
pub struct Tls {
/// True if accepting TLS connections should be enabled.
pub enabled: bool,
/// Path to certificate `.pem` file.
pub certificate: PathBuf,
/// Path to private key `.pem` file.
pub private_key: PathBuf,
}
impl Tls {
/// Returns an [`SslAcceptorBuilder`] with the configured settings.
///
/// The result is often used with [`actix_web::HttpServer::bind_openssl()`].
///
/// # Example
///
/// ```no_run
/// use std::io;
/// use actix_settings::{ApplySettings as _, Settings};
/// use actix_web::{get, web, App, HttpServer, Responder};
///
/// #[actix_web::main]
/// async fn main() -> io::Result<()> {
/// let settings = Settings::from_default_template();
///
/// HttpServer::new(|| {
/// App::new().route("/", web::to(|| async { "Hello, World!" }))
/// })
/// .try_apply_settings(&settings)?
/// .bind(("127.0.0.1", 8080))?
/// .bind_openssl(("127.0.0.1", 8443), settings.actix.tls.get_ssl_acceptor_builder()?)?
/// .run()
/// .await
/// }
/// ```
pub fn get_ssl_acceptor_builder(&self) -> AsResult<SslAcceptorBuilder> {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
builder.set_certificate_chain_file(&self.certificate)?;
builder.set_private_key_file(&self.private_key, SslFiletype::PEM)?;
builder.check_private_key()?;
Ok(builder)
}
}

View File

@ -1,70 +1,97 @@
# Changes
## Unreleased - 2022-xx-xx
## Unreleased
## 0.8.2
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.8.1
- Implement `From<Basic>` for `BasicAuth`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.8.0
- Removed `AuthExtractor` trait; implement `FromRequest` for your custom auth types. [#264]
- `BasicAuth::user_id()` now returns `&str`. [#249]
- `BasicAuth::password()` now returns `Option<&str>`. [#249]
- `Basic::user_id()` now returns `&str`. [#264]
- `Basic::password()` now returns `Option<&str>`. [#264]
- `Bearer::token()` now returns `&str`. [#264]
[#249]: https://github.com/actix/actix-extras/pull/249
[#264]: https://github.com/actix/actix-extras/pull/264
## 0.7.0
- Auth validator functions now need to return `(Error, ServiceRequest)` in error cases. [#260]
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
[#260]: https://github.com/actix/actix-extras/pull/260
## 0.6.0
## 0.6.0 - 2022-03-01
- Update `actix-web` dependency to `4`.
## 0.6.0-beta.8
## 0.6.0-beta.8 - 2022-02-07
- Relax body type bounds on middleware impl. [#223]
- Update `actix-web` dependency to `4.0.0-rc.1`.
[#223]: https://github.com/actix/actix-extras/pull/223
## 0.6.0-beta.7
## 0.6.0-beta.7 - 2021-12-29
- Minimum supported Rust version (MSRV) is now 1.54.
## 0.6.0-beta.6
## 0.6.0-beta.6 - 2021-12-18
- Update `actix-web` dependency to `4.0.0.beta-15`. [#216]
[#216]: https://github.com/actix/actix-extras/pull/216
## 0.6.0-beta.5
## 0.6.0-beta.5 - 2021-12-12
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.6.0-beta.4
## 0.6.0-beta.4 - 2021-11-22
- impl `AuthExtractor` trait for `Option<T: AuthExtractor>` and `Result<T: AuthExtractor, T::Error>`. [#205]
[#205]: https://github.com/actix/actix-extras/pull/205
## 0.6.0-beta.3
## 0.6.0-beta.3 - 2021-10-21
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.6.0-beta.2
## 0.6.0-beta.2 - 2021-06-27
- No notable changes.
## 0.6.0-beta.1
## 0.6.0-beta.1 - 2021-04-02
- Update `actix-web` dependency to 4.0.0 beta.
- Minimum supported Rust version (MSRV) is now 1.46.0.
## 0.5.1
## 0.5.1 - 2021-03-21
- Correct error handling when extracting auth details from request. [#128]
[#128]: https://github.com/actix/actix-extras/pull/128
## 0.5.0
## 0.5.0 - 2020-09-11
- Update `actix-web` dependency to 3.0.0.
- Minimum supported Rust version (MSRV) is now 1.42.0.
## 0.4.2
## 0.4.2 - 2020-07-08
- Update the `base64` dependency to 0.12
- AuthenticationError's status code is preserved when converting to a ResponseError
- Minimize `futures` dependency
@ -72,46 +99,46 @@
[#69]: https://github.com/actix/actix-web-httpauth/pull/69
## 0.4.1
## 0.4.1 - 2020-02-19
- Move repository to actix-extras
## 0.4.0
## 0.4.0 - 2020-01-14
- Depends on `actix-web = "^2.0"`, `actix-service = "^1.0"`, and `futures = "^0.3"` version now ([#14])
- Depends on `bytes = "^0.5"` and `base64 = "^0.11"` now
[#14]: https://github.com/actix/actix-web-httpauth/pull/14
## 0.3.2 - 2019-07-19
- Middleware accepts any `Fn` as a validator function instead of `FnMut` [#11]
[#11]: https://github.com/actix/actix-web-httpauth/pull/11
## 0.3.1 - 2019-06-09
- Multiple calls to the middleware would result in panic
## 0.3.0 - 2019-06-05
- Crate edition was changed to `2018`, same as `actix-web`
- Depends on `actix-web = "^1.0"` version now
- `WWWAuthenticate` header struct was renamed into `WwwAuthenticate`
- Challenges and extractor configs are now operating with `Cow<'static, str>` types instead of `String` types
## 0.2.0 - 2019-04-26
- `actix-web` dependency is used without default features now [#6]
- `base64` dependency version was bumped to `0.10`
[#6]: https://github.com/actix/actix-web-httpauth/pull/6
## 0.1.0 - 2018-09-08
- Update to `actix-web = "0.7"` version
## 0.0.4 - 2018-07-01
- Fix possible panic at `IntoHeaderValue` implementation for `headers::authorization::Basic`
- Fix possible panic at `headers::www_authenticate::challenge::bearer::Bearer::to_bytes` call

View File

@ -1,32 +1,39 @@
[package]
name = "actix-web-httpauth"
version = "0.6.0"
version = "0.8.2"
description = "HTTP authentication schemes for Actix Web"
categories = ["web-programming"]
keywords = ["http", "web", "framework", "authentication", "security"]
authors = [
"svartalf <self@svartalf.info>",
"Yuki Okushi <huyuumi.dev@gmail.com>",
]
description = "HTTP authentication schemes for Actix Web"
keywords = ["http", "web", "framework", "authentication", "security"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
categories = ["web-programming::http-server"]
license = "MIT OR Apache-2.0"
edition = "2018"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
name = "actix_web_httpauth"
path = "src/lib.rs"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[dependencies]
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4", default_features = false }
actix-web = { version = "4.1", default-features = false }
base64 = "0.13"
futures-util = { version = "0.3.7", default-features = false }
futures-core = { version = "0.3.7", default-features = false }
base64 = "0.22"
futures-core = "0.3.17"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
log = "0.4"
pin-project-lite = "0.2.7"
[dev-dependencies]
actix-cors = "0.6.0"
actix-web = { version = "4", default_features = false, features = ["macros"] }
actix-cors = "0.7"
actix-service = "2"
actix-web = { version = "4.1", default-features = false, features = ["macros"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = "0.1.30"
[lints]
workspace = true

Some files were not shown because too many files have changed in this diff Show More