Files
Ophir LOJKINE 625deb973b Add and document sqlpage.hmac function (#1033)
* feat: Add sqlpage.hmac function for cryptographic signing

Co-authored-by: contact <contact@ophir.dev>

* feat: Add sqlpage.hmac function for secure data signing

Co-authored-by: contact <contact@ophir.dev>

* Test HMAC function with RFC vectors and update tests

Co-authored-by: contact <contact@ophir.dev>

* feat: Add sqlpage.hmac() function for secure signatures

Co-authored-by: contact <contact@ophir.dev>

* feat: Add base64 output option to hmac function

Co-authored-by: contact <contact@ophir.dev>

* Refactor hmac function for cleaner output formatting

Co-authored-by: contact <contact@ophir.dev>

* Add webhook HMAC signature validation tests

Co-authored-by: contact <contact@ophir.dev>

* Refactor HMAC function and update SQL examples for clarity and consistency

- Changed function parameters to remove Option types for data and key in the HMAC function.
- Improved SQL documentation and examples for HMAC usage, including clearer descriptions and updated error handling.
- Enhanced test cases for webhook HMAC validation to ensure accurate signature checks and responses.
- Removed obsolete test file for HMAC with null values.

* Update HMAC validation logic to handle NULL values in SQL queries

- Modified conditions in SQL queries to check for NULL values alongside signature mismatches.
- Enhanced documentation on NULL handling for HMAC checks to improve clarity and portability.

* remove debug logging from ci

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2025-10-01 21:07:36 +02:00

124 lines
4.2 KiB
Rust

use actix_web::{http::StatusCode, test};
use sqlpage::webserver::http::main_handler;
use crate::common::get_request_to;
#[actix_web::test]
async fn test_request_body() -> actix_web::Result<()> {
let req = get_request_to("/tests/requests/request_body_test.sql")
.await?
.insert_header(("content-type", "text/plain"))
.set_payload("Hello, world!")
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
body_str.contains("Hello, world!"),
"{body_str}\nexpected to contain: Hello, world!"
);
// Test with form data - should return NULL
let req = get_request_to("/tests/requests/request_body_test.sql")
.await?
.insert_header(("content-type", "application/x-www-form-urlencoded"))
.set_payload("key=value")
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
body_str.contains("NULL"),
"{body_str}\nexpected NULL for form data"
);
Ok(())
}
#[actix_web::test]
async fn test_request_body_base64() -> actix_web::Result<()> {
let binary_data = (0u8..=255u8).collect::<Vec<_>>();
let expected_base64 =
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &binary_data);
let req = get_request_to("/tests/requests/request_body_base64_test.sql")
.await?
.insert_header(("content-type", "application/octet-stream"))
.set_payload(binary_data)
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
body_str.contains(&expected_base64),
"{body_str}\nexpected to contain base64: {expected_base64}"
);
// Test with form data - should return NULL
let req = get_request_to("/tests/requests/request_body_base64_test.sql")
.await?
.insert_header(("content-type", "application/x-www-form-urlencoded"))
.set_payload("key=value")
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
body_str.contains("NULL"),
"{body_str}\nexpected NULL for form data"
);
Ok(())
}
#[actix_web::test]
async fn test_download_data_url() -> actix_web::Result<()> {
let req = get_request_to("/tests/requests/request_download_test.sql")
.await?
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp.headers().get("content-type").unwrap();
assert_eq!(ct, "text/plain");
let content_disposition = resp.headers().get("content-disposition").unwrap();
assert_eq!(
content_disposition,
"attachment; filename=\"my text file.txt\""
);
let body = test::read_body(resp).await;
assert_eq!(&body, &b"Hello download!"[..]);
Ok(())
}
#[actix_web::test]
async fn test_large_form_field_roundtrip() -> actix_web::Result<()> {
let long_string = "a".repeat(123454);
let req = get_request_to("/tests/components/display_form_field.sql")
.await?
.insert_header(("content-type", "application/x-www-form-urlencoded"))
.set_payload(["x=", &long_string].concat()) // total size is 123454 + 2 = 123456 bytes
.to_srv_request();
let resp = main_handler(req).await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
!body_str.contains("error"),
"{body_str}\nshouldn't have errors"
);
assert!(
body_str.contains(&long_string),
"{body_str}\nexpected to contain long string submitted"
);
Ok(())
}
mod webhook_hmac;