Compare commits

...

2 Commits

Author SHA1 Message Date
xfy
8014e202f8 ci: use aliyun rust mirror container
Some checks failed
CI / check (push) Failing after 1s
CI / build (push) Has been skipped
2026-06-12 17:03:38 +08:00
xfy
4ae7b38131 feat(auth): add Secure flag to session cookie via COOKIE_SECURE env 2026-06-12 17:02:22 +08:00
3 changed files with 50 additions and 24 deletions

View File

@ -7,12 +7,13 @@ on:
branches: [main, master] branches: [main, master]
env: env:
RUSTUP_UPDATE_ROOT: https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup CARGO_NET_OFFLINE: false
RUSTUP_DIST_SERVER: https://mirrors.tuna.tsinghua.edu.cn/rustup
jobs: jobs:
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: registry.cn-hangzhou.aliyuncs.com/library/rust:bookworm
steps: steps:
- name: Checkout - name: Checkout
run: | run: |
@ -24,7 +25,7 @@ jobs:
- name: Configure Cargo mirror - name: Configure Cargo mirror
run: | run: |
mkdir -p "$HOME/.cargo" mkdir -p "$HOME/.cargo"
cat > "$HOME/.cargo/config.toml" <>EOF cat > "$HOME/.cargo/config.toml" <<EOF
[source.crates-io] [source.crates-io]
replace-with = 'ustc' replace-with = 'ustc'
@ -32,11 +33,6 @@ jobs:
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/" registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
EOF EOF
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Node.js - name: Install Node.js
run: | run: |
apt-get update apt-get update
@ -65,10 +61,11 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: registry.cn-hangzhou.aliyuncs.com/library/rust:bookworm
needs: check needs: check
env: env:
RUSTUP_UPDATE_ROOT: https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup CARGO_NET_OFFLINE: false
RUSTUP_DIST_SERVER: https://mirrors.tuna.tsinghua.edu.cn/rustup
steps: steps:
- name: Checkout - name: Checkout
run: | run: |
@ -80,7 +77,7 @@ jobs:
- name: Configure Cargo mirror - name: Configure Cargo mirror
run: | run: |
mkdir -p "$HOME/.cargo" mkdir -p "$HOME/.cargo"
cat > "$HOME/.cargo/config.toml" <>EOF cat > "$HOME/.cargo/config.toml" <<EOF
[source.crates-io] [source.crates-io]
replace-with = 'ustc' replace-with = 'ustc'
@ -88,11 +85,6 @@ jobs:
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/" registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
EOF EOF
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Node.js - name: Install Node.js
run: | run: |
apt-get update apt-get update

View File

@ -229,10 +229,7 @@ pub async fn login(username: String, password: String) -> Result<AuthResponse, S
.await .await
.map_err(AppError::query)?; .map_err(AppError::query)?;
let cookie = format!( let cookie = session::session_cookie(&token, 30 * 24 * 60 * 60, session::cookie_secure());
"session={token}; HttpOnly; Path=/; Max-Age={}; SameSite=Lax",
30 * 24 * 60 * 60
);
if let Some(ctx) = dioxus::fullstack::FullstackContext::current() { if let Some(ctx) = dioxus::fullstack::FullstackContext::current() {
if let Ok(value) = HeaderValue::try_from(cookie.as_str()) { if let Ok(value) = HeaderValue::try_from(cookie.as_str()) {
ctx.add_response_header(SET_COOKIE, value); ctx.add_response_header(SET_COOKIE, value);
@ -252,11 +249,11 @@ pub async fn logout() -> Result<AuthResponse, ServerFnError> {
let client = get_conn().await.map_err(AppError::db_conn)?; let client = get_conn().await.map_err(AppError::db_conn)?;
let cookie = session::session_cookie("", 0, session::cookie_secure());
if let Some(ctx) = dioxus::fullstack::FullstackContext::current() { if let Some(ctx) = dioxus::fullstack::FullstackContext::current() {
ctx.add_response_header( if let Ok(value) = HeaderValue::try_from(cookie.as_str()) {
SET_COOKIE, ctx.add_response_header(SET_COOKIE, value);
HeaderValue::from_static("session=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax"), }
);
} }
if let Some(t) = token { if let Some(t) = token {

View File

@ -19,6 +19,22 @@ pub fn default_expiry() -> DateTime<Utc> {
Utc::now() + Duration::days(30) Utc::now() + Duration::days(30)
} }
#[cfg(feature = "server")]
pub fn cookie_secure() -> bool {
std::env::var("COOKIE_SECURE")
.ok()
.map(|v| matches!(v.as_str(), "1" | "true" | "yes"))
.unwrap_or(false)
}
#[cfg(feature = "server")]
pub fn session_cookie(token: &str, max_age_seconds: i32, secure: bool) -> String {
let secure_flag = if secure { "; Secure" } else { "" };
format!(
"session={token}; HttpOnly; Path=/; Max-Age={max_age_seconds}; SameSite=Lax{secure_flag}"
)
}
#[cfg(feature = "server")] #[cfg(feature = "server")]
pub fn parse_session_token(cookie_header: &str) -> Option<&str> { pub fn parse_session_token(cookie_header: &str) -> Option<&str> {
cookie_header.split(';').map(|s| s.trim()).find_map(|pair| { cookie_header.split(';').map(|s| s.trim()).find_map(|pair| {
@ -132,4 +148,25 @@ mod tests {
let expected = sha2::Sha256::digest(b"hello"); let expected = sha2::Sha256::digest(b"hello");
assert_eq!(hash, hex::encode(expected)); assert_eq!(hash, hex::encode(expected));
} }
#[test]
fn session_cookie_without_secure() {
let cookie = session_cookie("abc", 3600, false);
assert!(cookie.contains("session=abc"));
assert!(cookie.contains("HttpOnly"));
assert!(cookie.contains("SameSite=Lax"));
assert!(!cookie.contains("Secure"));
}
#[test]
fn session_cookie_with_secure() {
let cookie = session_cookie("abc", 3600, true);
assert!(cookie.contains("Secure"));
}
#[test]
fn session_cookie_logout_has_zero_max_age() {
let cookie = session_cookie("", 0, false);
assert!(cookie.contains("Max-Age=0"));
}
} }