+145 -34 +/-6 browse
1 | diff --git a/Cargo.lock b/Cargo.lock |
2 | index 51e6494..3fcba1a 100644 |
3 | --- a/Cargo.lock |
4 | +++ b/Cargo.lock |
5 | @@ -2035,6 +2035,7 @@ dependencies = [ |
6 | "rand", |
7 | "serde", |
8 | "serde_json", |
9 | + "serde_urlencoded", |
10 | "tempfile", |
11 | "tokio", |
12 | "tower", |
13 | diff --git a/mailpot-tests/for_testing.db b/mailpot-tests/for_testing.db |
14 | index 70ccedb..64f74ee 100644 |
15 | Binary files a/mailpot-tests/for_testing.db and b/mailpot-tests/for_testing.db differ |
16 | diff --git a/rest-http/src/routes/list.rs b/rest-http/src/routes/list.rs |
17 | index c9947f0..1897ce2 100644 |
18 | --- a/rest-http/src/routes/list.rs |
19 | +++ b/rest-http/src/routes/list.rs |
20 | @@ -340,7 +340,7 @@ mod tests { |
21 | list: 1, |
22 | address: "user@example.com".to_string(), |
23 | name: Some("Name".to_string()), |
24 | - account: None, |
25 | + account: Some(1), |
26 | enabled: true, |
27 | verified: false, |
28 | digest: false, |
29 | diff --git a/web/Cargo.toml b/web/Cargo.toml |
30 | index 597c199..a203fc7 100644 |
31 | --- a/web/Cargo.toml |
32 | +++ b/web/Cargo.toml |
33 | @@ -43,7 +43,8 @@ zstd = { version = "0.12", default-features = false } |
34 | [dev-dependencies] |
35 | hyper = { version = "0.14" } |
36 | mailpot-tests = { version = "^0.1", path = "../mailpot-tests" } |
37 | - tempfile = "3.3" |
38 | + serde_urlencoded = { version = "^0.7" } |
39 | + tempfile = { version = "3.3" } |
40 | tower = { version = "^0.4" } |
41 | |
42 | [build-dependencies] |
43 | diff --git a/web/src/auth.rs b/web/src/auth.rs |
44 | index 84d3ef6..af17e30 100644 |
45 | --- a/web/src/auth.rs |
46 | +++ b/web/src/auth.rs |
47 | @@ -179,7 +179,7 @@ pub async fn ssh_signin_post( |
48 | let (_prev_token, _) = if let Some(tok @ (_, timestamp)) = |
49 | session.get::<(String, i64)>(TOKEN_KEY) |
50 | { |
51 | - if !(timestamp < now && now - timestamp < EXPIRY_IN_SECS) { |
52 | + if !(timestamp <= now && now - timestamp < EXPIRY_IN_SECS) { |
53 | session.add_message(Message { |
54 | message: "The token has expired. Please retry.".into(), |
55 | level: Level::Error, |
56 | diff --git a/web/src/main.rs b/web/src/main.rs |
57 | index a6de0c7..000d58e 100644 |
58 | --- a/web/src/main.rs |
59 | +++ b/web/src/main.rs |
60 | @@ -26,12 +26,8 @@ use minijinja::value::Value; |
61 | use rand::Rng; |
62 | use tokio::sync::RwLock; |
63 | |
64 | - fn create_app(conf: Configuration) -> Router { |
65 | - let store = MemoryStore::new(); |
66 | - let secret = rand::thread_rng().gen::<[u8; 128]>(); |
67 | - let session_layer = SessionLayer::new(store, &secret).with_secure(false); |
68 | - |
69 | - let shared_state = Arc::new(AppState { |
70 | + fn new_state(conf: Configuration) -> Arc<AppState> { |
71 | + Arc::new(AppState { |
72 | conf, |
73 | root_url_prefix: Value::from_safe_string( |
74 | std::env::var("ROOT_URL_PREFIX").unwrap_or_default(), |
75 | @@ -42,7 +38,13 @@ fn create_app(conf: Configuration) -> Router { |
76 | .into(), |
77 | site_subtitle: std::env::var("SITE_SUBTITLE").ok().map(Into::into), |
78 | user_store: Arc::new(RwLock::new(HashMap::default())), |
79 | - }); |
80 | + }) |
81 | + } |
82 | + |
83 | + fn create_app(shared_state: Arc<AppState>) -> Router { |
84 | + let store = MemoryStore::new(); |
85 | + let secret = rand::thread_rng().gen::<[u8; 128]>(); |
86 | + let session_layer = SessionLayer::new(store, &secret).with_secure(false); |
87 | |
88 | let auth_layer = AuthLayer::new(shared_state.clone(), &secret); |
89 | |
90 | @@ -155,7 +157,7 @@ async fn main() { |
91 | return; |
92 | } |
93 | let conf = Configuration::from_file(config_path).unwrap(); |
94 | - let app = create_app(conf); |
95 | + let app = create_app(new_state(conf)); |
96 | |
97 | let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "0.0.0.0".to_string()); |
98 | let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string()); |
99 | @@ -215,7 +217,11 @@ mod tests { |
100 | |
101 | use axum::{ |
102 | body::Body, |
103 | - http::{method::Method, Request, StatusCode}, |
104 | + http::{ |
105 | + header::{COOKIE, SET_COOKIE}, |
106 | + method::Method, |
107 | + Request, StatusCode, |
108 | + }, |
109 | }; |
110 | use mailpot::{Configuration, Connection, SendMail}; |
111 | use mailpot_tests::init_stderr_logging; |
112 | @@ -228,6 +234,26 @@ mod tests { |
113 | async fn test_routes() { |
114 | init_stderr_logging(); |
115 | |
116 | + macro_rules! req { |
117 | + (get $url:expr) => {{ |
118 | + Request::builder() |
119 | + .uri($url) |
120 | + .method(Method::GET) |
121 | + .body(Body::empty()) |
122 | + .unwrap() |
123 | + }}; |
124 | + (post $url:expr, $body:expr) => {{ |
125 | + Request::builder() |
126 | + .uri($url) |
127 | + .method(Method::POST) |
128 | + .header("Content-Type", "application/x-www-form-urlencoded") |
129 | + .body(Body::from( |
130 | + serde_urlencoded::to_string($body).unwrap().into_bytes(), |
131 | + )) |
132 | + .unwrap() |
133 | + }}; |
134 | + } |
135 | + |
136 | let tmp_dir = TempDir::new().unwrap(); |
137 | |
138 | let db_path = tmp_dir.path().join("mpot.db"); |
139 | @@ -241,46 +267,129 @@ mod tests { |
140 | let db = Connection::open_db(config.clone()).unwrap(); |
141 | let list = db.lists().unwrap().remove(0); |
142 | |
143 | + let state = new_state(config.clone()); |
144 | + |
145 | // ------------------------------------------------------------ |
146 | // list() |
147 | |
148 | - let cl = |url, config| async move { |
149 | - let response = create_app(config) |
150 | - .oneshot( |
151 | - Request::builder() |
152 | - .uri(&url) |
153 | - .method(Method::GET) |
154 | - .body(Body::empty()) |
155 | - .unwrap(), |
156 | - ) |
157 | - .await |
158 | - .unwrap(); |
159 | + let cl = |url, state| async move { |
160 | + let response = create_app(state).oneshot(req!(get & url)).await.unwrap(); |
161 | |
162 | assert_eq!(response.status(), StatusCode::OK); |
163 | |
164 | hyper::body::to_bytes(response.into_body()).await.unwrap() |
165 | }; |
166 | assert_eq!( |
167 | - cl(format!("/list/{}/", list.id), config.clone()).await, |
168 | - cl(format!("/list/{}/", list.pk), config.clone()).await |
169 | + cl(format!("/list/{}/", list.id), state.clone()).await, |
170 | + cl(format!("/list/{}/", list.pk), state.clone()).await |
171 | ); |
172 | |
173 | // ------------------------------------------------------------ |
174 | // help(), ssh_signin(), root() |
175 | |
176 | - for path in ["/help/", "/login/", "/"] { |
177 | - let response = create_app(config.clone()) |
178 | - .oneshot( |
179 | - Request::builder() |
180 | - .uri(path) |
181 | - .method(Method::GET) |
182 | - .body(Body::empty()) |
183 | - .unwrap(), |
184 | - ) |
185 | + for path in ["/help/", "/"] { |
186 | + let response = create_app(state.clone()) |
187 | + .oneshot(req!(get path)) |
188 | + .await |
189 | + .unwrap(); |
190 | + |
191 | + assert_eq!(response.status(), StatusCode::OK); |
192 | + } |
193 | + |
194 | + // ------------------------------------------------------------ |
195 | + // auth.rs... |
196 | + |
197 | + let login_app = create_app(state.clone()); |
198 | + let session_cookie = { |
199 | + let response = login_app |
200 | + .clone() |
201 | + .oneshot(req!(get "/login/")) |
202 | .await |
203 | .unwrap(); |
204 | + assert_eq!(response.status(), StatusCode::OK); |
205 | + |
206 | + response.headers().get(SET_COOKIE).unwrap().clone() |
207 | + }; |
208 | + let user = User { |
209 | + pk: 1, |
210 | + ssh_signature: String::new(), |
211 | + role: Role::User, |
212 | + public_key: None, |
213 | + password: String::new(), |
214 | + name: None, |
215 | + address: String::new(), |
216 | + enabled: true, |
217 | + }; |
218 | + state.insert_user(1, user.clone()).await; |
219 | + |
220 | + { |
221 | + let mut request = req!(post "/login/", |
222 | + AuthFormPayload { |
223 | + address: "user@example.com".into(), |
224 | + password: "hunter2".into() |
225 | + } |
226 | + ); |
227 | + request |
228 | + .headers_mut() |
229 | + .insert(COOKIE, session_cookie.to_owned()); |
230 | + let res = login_app.clone().oneshot(request).await.unwrap(); |
231 | + |
232 | + assert_eq!( |
233 | + res.headers().get(http::header::LOCATION), |
234 | + Some( |
235 | + &SettingsPath |
236 | + .to_uri() |
237 | + .to_string() |
238 | + .as_str() |
239 | + .try_into() |
240 | + .unwrap() |
241 | + ) |
242 | + ); |
243 | + } |
244 | + |
245 | + // ------------------------------------------------------------ |
246 | + // settings() |
247 | + |
248 | + { |
249 | + let mut request = req!(get "/settings/"); |
250 | + request |
251 | + .headers_mut() |
252 | + .insert(COOKIE, session_cookie.to_owned()); |
253 | + let response = login_app.clone().oneshot(request).await.unwrap(); |
254 | |
255 | assert_eq!(response.status(), StatusCode::OK); |
256 | } |
257 | + |
258 | + // ------------------------------------------------------------ |
259 | + // settings_post() |
260 | + |
261 | + { |
262 | + let mut request = req!( |
263 | + post "/settings/", |
264 | + crate::settings::ChangeSetting::Subscribe { |
265 | + list_pk: IntPOST(1), |
266 | + }); |
267 | + request |
268 | + .headers_mut() |
269 | + .insert(COOKIE, session_cookie.to_owned()); |
270 | + let res = login_app.clone().oneshot(request).await.unwrap(); |
271 | + |
272 | + assert_eq!( |
273 | + res.headers().get(http::header::LOCATION), |
274 | + Some( |
275 | + &SettingsPath |
276 | + .to_uri() |
277 | + .to_string() |
278 | + .as_str() |
279 | + .try_into() |
280 | + .unwrap() |
281 | + ) |
282 | + ); |
283 | + } |
284 | + // ------------------------------------------------------------ |
285 | + // user_list_subscription() TODO |
286 | + |
287 | + // ------------------------------------------------------------ |
288 | + // user_list_subscription_post() TODO |
289 | } |
290 | } |