diff --git a/Cargo.lock b/Cargo.lock index b03e980..b3cb7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "base64", + "base64 0.22.1", "bitflags", "brotli", "bytes", @@ -143,6 +143,23 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "actix-session" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe6976a74f34f1b6d07a6c05aadc0ed0359304a7781c367fa5b4029418db08f" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "anyhow", + "derive_more 1.0.0", + "rand 0.8.5", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "actix-utils" version = "3.0.1" @@ -235,6 +252,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -342,6 +394,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "autocfg" version = "1.5.0" @@ -363,6 +421,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.22.1" @@ -477,6 +541,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.41" @@ -555,7 +629,14 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ + "aes-gcm", + "base64 0.20.0", + "hkdf", + "hmac", "percent-encoding", + "rand 0.8.5", + "sha2", + "subtle", "time", "version_check", ] @@ -625,9 +706,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "deranged" version = "0.4.0" @@ -650,13 +741,34 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl", + "derive_more-impl 2.0.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", ] [[package]] @@ -679,6 +791,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -901,6 +1014,16 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -986,6 +1109,24 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -1178,6 +1319,15 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "intl-memoizer" version = "0.5.3" @@ -1405,6 +1555,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "overload" version = "0.1.1" @@ -1413,9 +1569,10 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.12" +version = "0.0.13" dependencies = [ "actix-files", + "actix-session", "actix-web", "actix-web-static-files", "chrono", @@ -1594,6 +1751,18 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -1930,6 +2099,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2023,6 +2203,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.104" @@ -2375,6 +2561,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.4" diff --git a/Cargo.toml b/Cargo.toml index 049c258..87b7f69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.12" +version = "0.0.13" edition = "2021" description = """\ @@ -35,6 +35,7 @@ fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } actix-web = "4.11.0" +actix-session = { version = "0.10.1", features = ["cookie-session"] } actix-web-files = { package = "actix-files", version = "0.6.6" } actix-web-static-files = "4.0.1" static-files.workspace = true diff --git a/src/app.rs b/src/app.rs index b6b5a71..6b11c0f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,6 +5,10 @@ mod figfont; use crate::core::{extension, extension::ExtensionRef}; use crate::{global, locale, service, trace}; +use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; +use actix_session::storage::CookieSessionStore; +use actix_session::SessionMiddleware; + use substring::Substring; use std::io::Error; @@ -111,9 +115,27 @@ impl Application { /// Devuelve [`std::io::Error`] si el *socket* no puede enlazarse (por puerto en uso, permisos, /// etc.). pub fn run(self) -> Result { + // Genera clave secreta para firmar y verificar cookies. + let secret_key = service::cookie::Key::generate(); + // Prepara el servidor web. Ok(service::HttpServer::new(move || { - Self::service_app().wrap(tracing_actix_web::TracingLogger::default()) + Self::service_app() + .wrap(tracing_actix_web::TracingLogger::default()) + .wrap( + SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone()) + .session_lifecycle(match global::SETTINGS.server.session_lifetime { + 0 => SessionLifecycle::BrowserSession(BrowserSession::default()), + _ => SessionLifecycle::PersistentSession( + PersistentSession::default().session_ttl( + service::cookie::time::Duration::seconds( + global::SETTINGS.server.session_lifetime, + ), + ), + ), + }) + .build(), + ) }) .bind(format!( "{}:{}", diff --git a/src/global.rs b/src/global.rs index e8f5065..3f78b50 100644 --- a/src/global.rs +++ b/src/global.rs @@ -6,23 +6,24 @@ use serde::Deserialize; include_config!(SETTINGS: Settings => [ // [app] - "app.name" => "Sample", - "app.description" => "Developed with the amazing PageTop framework.", - "app.theme" => "Basic", - "app.language" => "en-US", - "app.startup_banner" => "Slant", + "app.name" => "Sample", + "app.description" => "Developed with the amazing PageTop framework.", + "app.theme" => "Basic", + "app.language" => "en-US", + "app.startup_banner" => "Slant", // [log] - "log.enabled" => true, - "log.tracing" => "Info", - "log.rolling" => "Stdout", - "log.path" => "log", - "log.prefix" => "tracing.log", - "log.format" => "Full", + "log.enabled" => true, + "log.tracing" => "Info", + "log.rolling" => "Stdout", + "log.path" => "log", + "log.prefix" => "tracing.log", + "log.format" => "Full", // [server] - "server.bind_address" => "localhost", - "server.bind_port" => 8080, + "server.bind_address" => "localhost", + "server.bind_port" => 8080, + "server.session_lifetime" => 604_800, ]); #[derive(Debug, Deserialize)] @@ -80,4 +81,8 @@ pub struct Server { pub bind_address: String, /// Puerto de escucha del servidor web. pub bind_port: u16, + /// Duración de la cookie de sesión en segundos (p.ej. `604_800` para una semana). + /// + /// El valor `0` indica que la cookie permanecerá activa hasta que se cierre el navegador. + pub session_lifetime: i64, } diff --git a/src/prelude.rs b/src/prelude.rs index af2f511..6bacaf3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -34,7 +34,7 @@ pub use crate::locale::*; pub use crate::datetime::*; pub use crate::service; -pub use crate::service::{HttpRequest, HttpResponse}; +pub use crate::service::{HttpMessage, HttpRequest, HttpResponse}; pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; diff --git a/src/service.rs b/src/service.rs index cc32d56..6897a43 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,12 +1,13 @@ //! Gestión del servidor y servicios web (con [Actix Web](https://docs.rs/actix-web)). +pub use actix_session::Session; pub use actix_web::body::BoxBody; pub use actix_web::dev::Server; pub use actix_web::dev::ServiceFactory as Factory; pub use actix_web::dev::ServiceRequest as Request; pub use actix_web::dev::ServiceResponse as Response; -pub use actix_web::{http, rt, web}; -pub use actix_web::{App, Error, HttpRequest, HttpResponse, HttpServer}; +pub use actix_web::{cookie, http, rt, web}; +pub use actix_web::{App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer}; #[doc(hidden)] pub use actix_web::test;