Commit
+38 -10 +/-2 browse
1 | diff --git a/web/README.md b/web/README.md |
2 | index c54e80c..b7ecbd6 100644 |
3 | --- a/web/README.md |
4 | +++ b/web/README.md |
5 | @@ -18,3 +18,12 @@ The following environment variables can be defined to configure various settings |
6 | - `SITE_SUBTITLE`, default empty. |
7 | - `ROOT_URL_PREFIX`, default empty. |
8 | - `SSH_NAMESPACE`, default `lists.mailpot.rs`. |
9 | + - `SECRET`, randomly generated by default. |
10 | + Use the same value across process restarts if you want user session cookies not to expire. |
11 | + The secret must be a valid UTF-8 string that is 128 bytes long. |
12 | + |
13 | + You can generate one with the following command: |
14 | + |
15 | + ```shell |
16 | + head /dev/urandom | tr -dc A-Za-z0-9 | head -c128 |
17 | + ``` |
18 | diff --git a/web/src/main.rs b/web/src/main.rs |
19 | index 45520a4..6a9aa9c 100644 |
20 | --- a/web/src/main.rs |
21 | +++ b/web/src/main.rs |
22 | @@ -41,10 +41,10 @@ fn new_state(conf: Configuration) -> Arc<AppState> { |
23 | }) |
24 | } |
25 | |
26 | - fn create_app(shared_state: Arc<AppState>) -> Router { |
27 | + fn create_app(shared_state: Arc<AppState>, secret: Option<[u8; 128]>) -> Router { |
28 | let store = MemoryStore::new(); |
29 | - let secret = rand::thread_rng().gen::<[u8; 128]>(); |
30 | - let session_layer = SessionLayer::new(store, &secret).with_secure(false); |
31 | + let secret = secret.unwrap_or_else(|| rand::thread_rng().gen::<[u8; 128]>()); |
32 | + let session_layer = SessionLayer::new(store, &secret).with_secure(true); |
33 | |
34 | let auth_layer = AuthLayer::new(shared_state.clone(), &secret); |
35 | |
36 | @@ -157,6 +157,22 @@ async fn main() { |
37 | |
38 | return; |
39 | } |
40 | + let secret = if let Ok(var) = std::env::var("SECRET") { |
41 | + if var.as_bytes().len() != 128 { |
42 | + eprintln!("Environment variable SECRET must be 128 bytes long."); |
43 | + return; |
44 | + } |
45 | + match var.as_bytes().try_into() { |
46 | + Err(err) => { |
47 | + eprintln!("Environment variable SECRET must be 128 bytes long. Error: {err}"); |
48 | + return; |
49 | + } |
50 | + Ok(v) => Some(v), |
51 | + } |
52 | + } else { |
53 | + None |
54 | + }; |
55 | + std::env::remove_var("SECRET"); |
56 | #[cfg(test)] |
57 | let verbosity = log::LevelFilter::Trace; |
58 | #[cfg(not(test))] |
59 | @@ -169,7 +185,7 @@ async fn main() { |
60 | .init() |
61 | .unwrap(); |
62 | let conf = Configuration::from_file(config_path).unwrap(); |
63 | - let app = create_app(new_state(conf)); |
64 | + let app = create_app(new_state(conf), secret); |
65 | |
66 | let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "0.0.0.0".to_string()); |
67 | let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string()); |
68 | @@ -292,7 +308,10 @@ mod tests { |
69 | // list() |
70 | |
71 | let cl = |url, state| async move { |
72 | - let response = create_app(state).oneshot(req!(get & url)).await.unwrap(); |
73 | + let response = create_app(state, None) |
74 | + .oneshot(req!(get & url)) |
75 | + .await |
76 | + .unwrap(); |
77 | |
78 | assert_eq!(response.status(), StatusCode::OK); |
79 | |
80 | @@ -308,7 +327,7 @@ mod tests { |
81 | |
82 | { |
83 | let msg_id = "<abcdefgh@sator.example.com>"; |
84 | - let res = create_app(state.clone()) |
85 | + let res = create_app(state.clone(), None) |
86 | .oneshot(req!( |
87 | get & format!( |
88 | "/list/{id}/posts/{msgid}/", |
89 | @@ -324,7 +343,7 @@ mod tests { |
90 | res.headers().get(http::header::CONTENT_TYPE), |
91 | Some(&http::HeaderValue::from_static("text/html; charset=utf-8")) |
92 | ); |
93 | - let res = create_app(state.clone()) |
94 | + let res = create_app(state.clone(), None) |
95 | .oneshot(req!( |
96 | get & format!( |
97 | "/list/{id}/posts/{msgid}/raw/", |
98 | @@ -340,7 +359,7 @@ mod tests { |
99 | res.headers().get(http::header::CONTENT_TYPE), |
100 | Some(&http::HeaderValue::from_static("text/plain; charset=utf-8")) |
101 | ); |
102 | - let res = create_app(state.clone()) |
103 | + let res = create_app(state.clone(), None) |
104 | .oneshot(req!( |
105 | get & format!( |
106 | "/list/{id}/posts/{msgid}/eml/", |
107 | @@ -367,7 +386,7 @@ mod tests { |
108 | // help(), ssh_signin(), root() |
109 | |
110 | for path in ["/help/", "/"] { |
111 | - let response = create_app(state.clone()) |
112 | + let response = create_app(state.clone(), None) |
113 | .oneshot(req!(get path)) |
114 | .await |
115 | .unwrap(); |
116 | @@ -378,7 +397,7 @@ mod tests { |
117 | // ------------------------------------------------------------ |
118 | // auth.rs... |
119 | |
120 | - let login_app = create_app(state.clone()); |
121 | + let login_app = create_app(state.clone(), None); |
122 | let session_cookie = { |
123 | let response = login_app |
124 | .clone() |