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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
||||
+12
-1
@@ -373,8 +373,19 @@ pub fn store_secret_key(
|
||||
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}"))?;
|
||||
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);
|
||||
if safe.is_empty() || !safe.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
if safe.is_empty() {
|
||||
return Err("Invalid OpenPGP fingerprint".to_string());
|
||||
}
|
||||
let final_path = dir.join(format!("{safe}.asc"));
|
||||
|
||||
@@ -41,7 +41,7 @@ struct MessageResponse {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 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).
|
||||
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.
|
||||
let inner = Router::new()
|
||||
.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);
|
||||
|
||||
// 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.
|
||||
// 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 {
|
||||
host: session.smtp_host.clone(),
|
||||
port: session.smtp_port,
|
||||
|
||||
+16
-4
@@ -2002,18 +2002,30 @@ async fn send_returns_400_with_empty_body_and_subject() {
|
||||
}
|
||||
|
||||
#[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 data_dir = TempDir::new().unwrap();
|
||||
// Use config WITHOUT smtp_host
|
||||
let config = test_config_with_imap(
|
||||
static_dir.path().to_str().unwrap(),
|
||||
data_dir.path().to_str().unwrap(),
|
||||
);
|
||||
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);
|
||||
|
||||
let app = create_router(
|
||||
|
||||
Reference in new issue
Block a user