zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use lazy_static::lazy_static;

use actix_session::config::PersistentSession;
use actix_session::storage::CookieSessionStore;
// use actix_session::storage::RedisSessionStore;
use actix_session::storage::SessionStore;
use actix_session::SessionMiddleware;

use actix_web::cookie::Key;
use actix_web_flash_messages::{storage::CookieMessageStore, FlashMessagesFramework};

lazy_static! {
    static ref DEFAULT_SESSION_TTL: u16 = 7;
    static ref DEFAULT_SESSION_STORE_TYPE: String = "cookie_session_store".to_string();
    static ref DEFAULT_SESSION_COOKIE_NAME: String = "RSESSIONIDs".to_string();
    static ref DEFAULT_FLASH_MESSAGE_COOKIE_NAME: String = "RFLASHIDs".to_string();
    static ref DEFAULT_SECRET_KEY: String =
        "super-long-and-secret-random-key-needed-to-verify-message-integrity".to_string();
}

#[derive(serde::Deserialize, Clone, Debug)]
pub struct CookieSessionSettings {
    pub session_store_type: Option<String>,
    pub session_ttl: Option<u16>,
    pub session_cookie_name: Option<String>,
    pub http_only: Option<bool>,
    pub cookie_secure: Option<bool>,
    pub flash_message_cookie_name: Option<String>,
    pub secret_key: Option<String>,
}

impl Default for CookieSessionSettings {
    fn default() -> Self {
        Self {
            session_store_type: Some(DEFAULT_SESSION_STORE_TYPE.to_string()),
            session_ttl: Some(*DEFAULT_SESSION_TTL),
            session_cookie_name: Some(DEFAULT_SESSION_COOKIE_NAME.to_string()),
            http_only: Some(false),
            cookie_secure: Some(false),
            flash_message_cookie_name: Some(DEFAULT_FLASH_MESSAGE_COOKIE_NAME.to_string()),
            secret_key: Some(DEFAULT_SECRET_KEY.to_string()),
        }
    }
}

impl CookieSessionSettings {
    pub fn is_cookie_store(&self) -> bool {
        self.session_store_type.as_ref().unwrap() == "cookie_session_store"
    }

    pub fn is_redis_store(&self) -> bool {
        self.session_store_type.as_ref().unwrap() == "redis_session_store"
    }

    pub fn get(settings: &Option<CookieSessionSettings>) -> Self {
        let _default = Self::default();

        if settings.is_some() {
            let mut s = settings.clone().unwrap();

            if s.session_store_type.is_none() {
                s.session_store_type = _default.session_store_type;
            }

            if s.session_ttl.is_none() {
                s.session_ttl = _default.session_ttl;
            }

            if s.session_cookie_name.is_none() {
                s.session_cookie_name = _default.session_cookie_name;
            }

            if s.http_only.is_none() {
                s.http_only = _default.http_only;
            }

            if s.cookie_secure.is_none() {
                s.cookie_secure = _default.cookie_secure;
            }

            if s.flash_message_cookie_name.is_none() {
                s.flash_message_cookie_name = _default.flash_message_cookie_name;
            }

            if s.secret_key.is_none() {
                s.secret_key = _default.secret_key;
            }

            s
        } else {
            _default
        }
    }
}

pub fn flash_message(settings: &CookieSessionSettings) -> FlashMessagesFramework {
    let secret_key = Key::from(settings.secret_key.clone().unwrap().as_bytes());
    let cookie_store = CookieMessageStore::builder(secret_key.clone())
        .cookie_name(settings.flash_message_cookie_name.clone().unwrap())
        .build();
    FlashMessagesFramework::builder(cookie_store).build()
}

// CookieSessionStore 本身不能“服务端共享 Session”,但可以“客户端自带 Session(天然共享)”。
pub fn session_cookie_store(
    settings: &CookieSessionSettings,
) -> SessionMiddleware<impl SessionStore> {
    // 所有实例必须使用相同 key
    // 这里的 Key 就是用于 Cookie 加密与签名的密钥;
    // 只要不同实例使用同一个 Key,那么所有实例都能识别同一个用户的 Cookie;
    // 浏览器携带的 Cookie 在任何实例上都能成功解密和读取。
    // 确保所有实例在同一个域名下(否则 Cookie 不会跨实例发送)
    // 一定要用 secure Cookie(Secure + HttpOnly)
    // 禁用后,JavaScript 可访问 Cookie
    // HttpOnly = true(Actix 默认启用)
    // 浏览器对 Cookie 大小有上限(≈ 4 KB)
    let secret_key = Key::from(settings.secret_key.clone().unwrap().as_bytes());

    SessionMiddleware::builder(CookieSessionStore::default(), secret_key)
        .cookie_name(settings.session_cookie_name.clone().unwrap())
        // .cookie_secure(false) → 在 HTTP(非 HTTPS)环境中也能设置 Cookie;
        .cookie_secure(settings.cookie_secure.unwrap())
        // HttpOnly 的作用是:防止 JavaScript 通过 document.cookie 访问 Cookie。
        // 禁用后,JavaScript 可访问 Cookie(存在安全风险), 启用 HttpOnly 不会影响 axios 发送 Cookie。
        // 它只影响 JavaScript 是否能读取 Cookie 内容,不会阻止浏览器自动附带 Cookie。
        // 也就是说:console.log(document.cookie); // 看不到 HttpOnly Cookie
        // HttpOnly = “JS 不能看,但浏览器会带”。 目的:防止 XSS(跨站脚本攻击) 通过前端脚本窃取会话。
        .cookie_http_only(settings.http_only.unwrap())
        .session_lifecycle(PersistentSession::default().session_ttl(
            actix_web::cookie::time::Duration::days(settings.session_ttl.unwrap() as i64),
            // actix_web::cookie::time::Duration::hours(settings.session_ttl.unwrap() as i64),
            // actix_web::cookie::time::Duration::minutes(settings.session_ttl.unwrap() as i64),
        ))
        .build()
}

// pub fn session_redis_store(
//     settings: &CookieSessionSettings,
//     redis_store: RedisSessionStore,
// ) -> SessionMiddleware<impl SessionStore> {
//     let secret_key = Key::from(settings.secret_key.clone().unwrap().as_bytes());
//     SessionMiddleware::builder(redis_store.clone(), secret_key.clone())
//         .cookie_name(settings.session_cookie_name.clone().unwrap())
//         .cookie_secure(settings.cookie_secure.unwrap())
//         .cookie_http_only(settings.http_only.unwrap())
//         .session_lifecycle(PersistentSession::default().session_ttl(
//             actix_web::cookie::time::Duration::days(settings.session_ttl.unwrap() as i64),
//         ))
//         .build()
// }