1 parent
6c15491284
commit
31634543f0
6 files changed
+40
-6
No files matched your search
@@ -11,6 +11,7 @@
|
|||||||
- Updated UI/build contract tests for the modular JavaScript build and expanded interface-style validation.
|
- Updated UI/build contract tests for the modular JavaScript build and expanded interface-style validation.
|
||||||
- Fixed the 0.0.2 release build regression by centralizing `AppConfig::default()` and updating stale `AccountSession` test fixtures for the new session metadata fields.
|
- Fixed the 0.0.2 release build regression by centralizing `AppConfig::default()` and updating stale `AccountSession` test fixtures for the new session metadata fields.
|
||||||
- Updated UI contract tests and visual fixtures after removal of the legacy `ui/` directory.
|
- Updated UI contract tests and visual fixtures after removal of the legacy `ui/` directory.
|
||||||
|
- Fixed release-test regressions in OpenPGP fingerprint validation, protected system-folder recognition, nested `base_path` UI routing, and missing SMTP endpoint handling.
|
||||||
|
|
||||||
### 0.0.1
|
### 0.0.1
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -373,8 +373,19 @@ pub fn store_secret_key(
|
|||||||
let dir = private_key_dir(data_dir, user_hash);
|
let dir = private_key_dir(data_dir, user_hash);
|
||||||
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create PGP key directory: {e}"))?;
|
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create PGP key directory: {e}"))?;
|
||||||
set_dir_permissions(&dir)?;
|
set_dir_permissions(&dir)?;
|
||||||
|
|
||||||
|
// Reject path/control punctuation before normalization. `normalize_fingerprint`
|
||||||
|
// intentionally strips formatting characters, so validating only its output
|
||||||
|
// would turn values such as "../../escape" into a short hexadecimal string.
|
||||||
|
if fingerprint.is_empty()
|
||||||
|
|| !fingerprint
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_hexdigit() || c.is_ascii_whitespace() || c == ':')
|
||||||
|
{
|
||||||
|
return Err("Invalid OpenPGP fingerprint".to_string());
|
||||||
|
}
|
||||||
let safe = normalize_fingerprint(fingerprint);
|
let safe = normalize_fingerprint(fingerprint);
|
||||||
if safe.is_empty() || !safe.chars().all(|c| c.is_ascii_hexdigit()) {
|
if safe.is_empty() {
|
||||||
return Err("Invalid OpenPGP fingerprint".to_string());
|
return Err("Invalid OpenPGP fingerprint".to_string());
|
||||||
}
|
}
|
||||||
let final_path = dir.join(format!("{safe}.asc"));
|
let final_path = dir.join(format!("{safe}.asc"));
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ struct MessageResponse {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Folder names that must not be renamed or deleted.
|
/// Folder names that must not be renamed or deleted.
|
||||||
const SYSTEM_FOLDERS: &[&str] = &["INBOX"];
|
const SYSTEM_FOLDERS: &[&str] = &["INBOX", "Sent", "Drafts", "Trash", "Junk", "Spam"];
|
||||||
|
|
||||||
/// Returns `true` if the folder is a system folder (case-insensitive).
|
/// Returns `true` if the folder is a system folder (case-insensitive).
|
||||||
fn is_system_folder(name: &str) -> bool {
|
fn is_system_folder(name: &str) -> bool {
|
||||||
|
|||||||
@@ -494,6 +494,9 @@ pub fn create_router(
|
|||||||
// RustBox serves API and UI from the same Rust process.
|
// RustBox serves API and UI from the same Rust process.
|
||||||
let inner = Router::new()
|
let inner = Router::new()
|
||||||
.nest("/api", api_router)
|
.nest("/api", api_router)
|
||||||
|
// An explicit index route is required for the root of a nested base path
|
||||||
|
// (for example `/mail/`). The fallback still handles SPA deep links.
|
||||||
|
.route("/", get(crate::ui::serve_ui))
|
||||||
.fallback(crate::ui::serve_ui);
|
.fallback(crate::ui::serve_ui);
|
||||||
|
|
||||||
// Use the same base path for routing and authentication cookies.
|
// Use the same base path for routing and authentication cookies.
|
||||||
|
|||||||
@@ -117,6 +117,13 @@ pub async fn send_message_handler(
|
|||||||
|
|
||||||
// Build SMTP credentials from the authenticated account session.
|
// Build SMTP credentials from the authenticated account session.
|
||||||
// Server endpoints are resolved from RustBox configuration at login and stored in the session.
|
// Server endpoints are resolved from RustBox configuration at login and stored in the session.
|
||||||
|
// Normal login falls back from an unset smtp_host to the IMAP host, but reject
|
||||||
|
// an empty endpoint defensively for stale/legacy sessions.
|
||||||
|
if session.smtp_host.trim().is_empty() {
|
||||||
|
return Err(AppError::ServiceUnavailable(
|
||||||
|
"SMTP server is not configured for this account".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let smtp_creds = SmtpCredentials {
|
let smtp_creds = SmtpCredentials {
|
||||||
host: session.smtp_host.clone(),
|
host: session.smtp_host.clone(),
|
||||||
port: session.smtp_port,
|
port: session.smtp_port,
|
||||||
|
|||||||
+16
-4
@@ -2002,18 +2002,30 @@ async fn send_returns_400_with_empty_body_and_subject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn send_returns_503_when_smtp_not_configured() {
|
async fn send_returns_503_when_session_has_no_smtp_endpoint() {
|
||||||
let static_dir = setup_static_dir();
|
let static_dir = setup_static_dir();
|
||||||
let data_dir = TempDir::new().unwrap();
|
let data_dir = TempDir::new().unwrap();
|
||||||
// Use config WITHOUT smtp_host
|
|
||||||
let config = test_config_with_imap(
|
let config = test_config_with_imap(
|
||||||
static_dir.path().to_str().unwrap(),
|
static_dir.path().to_str().unwrap(),
|
||||||
data_dir.path().to_str().unwrap(),
|
data_dir.path().to_str().unwrap(),
|
||||||
);
|
);
|
||||||
let store = test_store();
|
let store = test_store();
|
||||||
let (browser_id, account_id, token) = setup_test_account(&store, "alice@example.com");
|
let browser_id = store.create_browser();
|
||||||
|
let email = "alice@example.com";
|
||||||
|
let (token, account_id) = store.add_account_to_browser(
|
||||||
|
&browser_id,
|
||||||
|
email.to_string(),
|
||||||
|
"password".to_string(),
|
||||||
|
crate::auth::user_data::hash_email(email),
|
||||||
|
"imap.example.com".to_string(),
|
||||||
|
993,
|
||||||
|
true,
|
||||||
|
String::new(),
|
||||||
|
587,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
let user_hash = crate::auth::user_data::hash_email("alice@example.com");
|
let user_hash = crate::auth::user_data::hash_email(email);
|
||||||
provision_user_db(data_dir.path().to_str().unwrap(), &user_hash);
|
provision_user_db(data_dir.path().to_str().unwrap(), &user_hash);
|
||||||
|
|
||||||
let app = create_router(
|
let app = create_router(
|
||||||
|
|||||||
Reference in new issue
Block a user