Skip to content

Commit b8be749

Browse files
committed
Fix client-ip logging and add a test.
1 parent 26207b3 commit b8be749

File tree

3 files changed

+165
-111
lines changed

3 files changed

+165
-111
lines changed

trailbase-core/src/app_state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl AppState {
137137
return &self.state.conn;
138138
}
139139

140-
pub(crate) fn logs_conn(&self) -> &trailbase_sqlite::Connection {
140+
pub fn logs_conn(&self) -> &trailbase_sqlite::Connection {
141141
return &self.state.logs_conn;
142142
}
143143

trailbase-core/src/logging.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub(super) fn sqlite_logger_make_span(request: &Request<Body>) -> Span {
6969
uri = %request.uri(),
7070
version = ?request.version(),
7171
host,
72-
client_ip = %client_ip.as_ref().map_or("", |s| s.as_str()),
72+
client_ip = client_ip.as_ref().map_or("", |s| s.as_str()),
7373
user_agent,
7474
referer,
7575
);

trailbase-core/tests/integration_test.rs

Lines changed: 163 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ use axum_test::multipart::MultipartForm;
44
use axum_test::TestServer;
55
use cookie::Cookie;
66
use std::rc::Rc;
7+
use tracing_subscriber::{
8+
filter::{self, LevelFilter},
9+
prelude::*,
10+
};
711
use trailbase_sqlite::params;
812

913
use trailbase_core::api::{create_user_handler, login_with_password, CreateUserRequest};
@@ -15,17 +19,20 @@ use trailbase_core::AppState;
1519
use trailbase_core::{DataDir, Server, ServerOptions};
1620

1721
#[test]
18-
fn test_record_apis() {
22+
fn integration_tests() {
1923
let runtime = Rc::new(
2024
tokio::runtime::Builder::new_multi_thread()
2125
.enable_all()
2226
.build()
2327
.unwrap(),
2428
);
2529

30+
let _ = runtime.block_on(test_record_apis());
31+
}
32+
33+
async fn test_record_apis() {
2634
let data_dir = temp_dir::TempDir::new().unwrap();
2735

28-
let _ = runtime.block_on(async move {
2936
let app = Server::init(ServerOptions {
3037
data_dir: DataDir(data_dir.path().to_path_buf()),
3138
address: "".to_string(),
@@ -41,137 +48,184 @@ fn test_record_apis() {
4148

4249
let state = app.state();
4350
let conn = state.conn();
51+
let logs_conn = state.logs_conn();
4452

4553
create_chat_message_app_tables(conn).await.unwrap();
4654
state.refresh_table_cache().await.unwrap();
4755

4856
let room = add_room(conn, "room0").await.unwrap();
4957
let password = "Secret!1!!";
58+
let client_ip = "22.11.22.11";
5059

5160
// Register message table as record API with moderator read access.
5261
add_record_api(
53-
&state,
54-
"messages_api",
55-
"message",
56-
Acls {
57-
authenticated: vec![PermissionFlag::Create, PermissionFlag::Read],
58-
..Default::default()
59-
},
60-
AccessRules {
61-
create: Some(
62-
"(SELECT 1 FROM room_members AS m WHERE _USER_.id = _REQ_._owner AND m.user = _USER_.id AND m.room = _REQ_.room)".to_string(),
63-
),
64-
..Default::default()
65-
},
66-
)
67-
.await.unwrap();
62+
&state,
63+
"messages_api",
64+
"message",
65+
Acls {
66+
authenticated: vec![PermissionFlag::Create, PermissionFlag::Read],
67+
..Default::default()
68+
},
69+
AccessRules {
70+
create: Some(
71+
"(SELECT 1 FROM room_members AS m WHERE _USER_.id = _REQ_._owner AND m.user = _USER_.id AND m.room = _REQ_.room)".to_string(),
72+
),
73+
..Default::default()
74+
},
75+
)
76+
.await.unwrap();
6877

6978
let user_x_email = "[email protected]";
7079
let user_x = create_user_for_test(&state, user_x_email, password)
71-
.await.unwrap()
80+
.await
81+
.unwrap()
7282
.into_bytes();
7383

74-
let user_x_token = login_with_password(&state, user_x_email, password).await.unwrap();
84+
let user_x_token = login_with_password(&state, user_x_email, password)
85+
.await
86+
.unwrap();
7587

7688
add_user_to_room(conn, user_x, room).await.unwrap();
7789

78-
let server = TestServer::new(app.router().clone()).unwrap();
79-
80-
{
81-
// User X can post to a JSON message.
82-
let test_response = server
83-
.post(&format!("/{RECORD_API_PATH}/messages_api"))
84-
.add_cookie(Cookie::new(
85-
COOKIE_AUTH_TOKEN,
86-
user_x_token.auth_token.clone(),
87-
))
88-
.json(&serde_json::json!({
89-
"_owner": id_to_b64(&user_x),
90-
"room": id_to_b64(&room),
91-
"data": "user_x message to room",
92-
}))
93-
.await;
94-
95-
assert_eq!(
96-
test_response.status_code(),
97-
StatusCode::OK,
98-
"{:?}",
99-
test_response
100-
);
101-
}
90+
// Set up logging
91+
let filter = || {
92+
filter::Targets::new()
93+
.with_target("tower_http::trace::on_response", LevelFilter::DEBUG)
94+
.with_target("tower_http::trace::on_request", LevelFilter::DEBUG)
95+
.with_target("tower_http::trace::make_span", LevelFilter::DEBUG)
96+
.with_default(LevelFilter::INFO)
97+
};
98+
99+
// This declares **where** tracing is being logged to, e.g. stderr, file, sqlite.
100+
let layer = tracing_subscriber::registry()
101+
.with(trailbase_core::logging::SqliteLogLayer::new(app.state()).with_filter(filter()));
102+
103+
let _ = layer
104+
.with(
105+
tracing_subscriber::fmt::layer()
106+
.compact()
107+
.with_filter(filter()),
108+
)
109+
.try_init();
102110

103111
{
104-
// User X can post a form message.
105-
let test_response = server
106-
.post(&format!("/{RECORD_API_PATH}/messages_api"))
107-
.add_cookie(Cookie::new(
108-
COOKIE_AUTH_TOKEN,
109-
user_x_token.auth_token.clone(),
110-
))
111-
.form(&serde_json::json!({
112-
"_owner": id_to_b64(&user_x),
113-
"room": id_to_b64(&room),
114-
"data": "user_x message to room",
115-
}))
116-
.await;
117-
118-
assert_eq!(test_response.status_code(), StatusCode::OK);
112+
let server = TestServer::new(app.router().clone()).unwrap();
113+
114+
{
115+
// User X can post to a JSON message.
116+
let test_response = server
117+
.post(&format!("/{RECORD_API_PATH}/messages_api"))
118+
.add_header("X-Forwarded-For", client_ip)
119+
.add_cookie(Cookie::new(
120+
COOKIE_AUTH_TOKEN,
121+
user_x_token.auth_token.clone(),
122+
))
123+
.json(&serde_json::json!({
124+
"_owner": id_to_b64(&user_x),
125+
"room": id_to_b64(&room),
126+
"data": "user_x message to room",
127+
}))
128+
.await;
129+
130+
assert_eq!(
131+
test_response.status_code(),
132+
StatusCode::OK,
133+
"{:?}",
134+
test_response
135+
);
136+
}
137+
138+
{
139+
// User X can post a form message.
140+
let test_response = server
141+
.post(&format!("/{RECORD_API_PATH}/messages_api"))
142+
.add_cookie(Cookie::new(
143+
COOKIE_AUTH_TOKEN,
144+
user_x_token.auth_token.clone(),
145+
))
146+
.form(&serde_json::json!({
147+
"_owner": id_to_b64(&user_x),
148+
"room": id_to_b64(&room),
149+
"data": "user_x message to room",
150+
}))
151+
.await;
152+
153+
assert_eq!(test_response.status_code(), StatusCode::OK);
154+
}
155+
156+
{
157+
// User X can post a multipart message.
158+
let form = MultipartForm::new()
159+
.add_text("_owner", id_to_b64(&user_x))
160+
.add_text("room", id_to_b64(&room))
161+
.add_text("data", "user_x message to room");
162+
163+
let test_response = server
164+
.post(&format!("/{RECORD_API_PATH}/messages_api"))
165+
.add_cookie(Cookie::new(
166+
COOKIE_AUTH_TOKEN,
167+
user_x_token.auth_token.clone(),
168+
))
169+
.multipart(form)
170+
.await;
171+
172+
assert_eq!(test_response.status_code(), StatusCode::OK);
173+
}
174+
175+
{
176+
// Add a second record API for the same table
177+
add_record_api(
178+
&state,
179+
"messages_api_yolo",
180+
"message",
181+
Acls {
182+
world: vec![PermissionFlag::Create, PermissionFlag::Read],
183+
..Default::default()
184+
},
185+
AccessRules::default(),
186+
)
187+
.await
188+
.unwrap();
189+
190+
// Anonymous can post to a JSON message (i.e. no credentials/tokens are attached).
191+
let test_response = server
192+
.post(&format!("/{RECORD_API_PATH}/messages_api_yolo"))
193+
.json(&serde_json::json!({
194+
// NOTE: Id must be not null and a random id would violate foreign key constraint as
195+
// defined by the `message` table.
196+
"_owner": id_to_b64(&user_x),
197+
"room": id_to_b64(&room),
198+
"data": "anonymous' message to room",
199+
}))
200+
.await;
201+
202+
assert_eq!(
203+
test_response.status_code(),
204+
StatusCode::OK,
205+
"{test_response:?}"
206+
);
207+
}
119208
}
120209

121-
{
122-
// User X can post a multipart message.
123-
let form = MultipartForm::new()
124-
.add_text("_owner", id_to_b64(&user_x))
125-
.add_text("room", id_to_b64(&room))
126-
.add_text("data", "user_x message to room");
127-
128-
let test_response = server
129-
.post(&format!("/{RECORD_API_PATH}/messages_api"))
130-
.add_cookie(Cookie::new(
131-
COOKIE_AUTH_TOKEN,
132-
user_x_token.auth_token.clone(),
133-
))
134-
.multipart(form)
135-
.await;
136-
137-
assert_eq!(test_response.status_code(), StatusCode::OK);
138-
}
210+
let logs_count: i64 = logs_conn
211+
.query_row("SELECT COUNT(*) FROM _logs", ())
212+
.await
213+
.unwrap()
214+
.unwrap()
215+
.get(0)
216+
.unwrap();
217+
assert!(logs_count > 0);
139218

140-
{
141-
// Add a second record API for the same table
142-
add_record_api(
143-
&state,
144-
"messages_api_yolo",
145-
"message",
146-
Acls {
147-
world: vec![PermissionFlag::Create, PermissionFlag::Read],
148-
..Default::default()
149-
},
150-
AccessRules::default(),
219+
let row = logs_conn
220+
.query_row(
221+
"SELECT client_ip FROM _logs WHERE client_ip = $1",
222+
trailbase_sqlite::params!(client_ip),
151223
)
152-
.await.unwrap();
153-
154-
// Anonymous can post to a JSON message (i.e. no credentials/tokens are attached).
155-
let test_response = server
156-
.post(&format!("/{RECORD_API_PATH}/messages_api_yolo"))
157-
.json(&serde_json::json!({
158-
// NOTE: Id must be not null and a random id would violate foreign key constraint as
159-
// defined by the `message` table.
160-
// "_owner": id_to_b64(&Uuid::now_v7().into_bytes()),
161-
"_owner": id_to_b64(&user_x),
162-
"room": id_to_b64(&room),
163-
"data": "anonymous' message to room",
164-
}))
165-
.await;
166-
167-
assert_eq!(
168-
test_response.status_code(),
169-
StatusCode::OK,
170-
"{test_response:?}"
171-
);
172-
}
224+
.await
225+
.unwrap()
226+
.unwrap();
173227

174-
});
228+
assert_eq!(row.get::<String>(0).unwrap(), client_ip);
175229
}
176230

177231
pub async fn create_chat_message_app_tables(

0 commit comments

Comments
 (0)