Release version 2.0.4

This commit is contained in:
shlomi
2026-01-15 03:00:25 +02:00
parent 6ee0bfc80d
commit 43a51d5848
6 changed files with 59 additions and 9 deletions

View File

@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.4] - 2026-01-15
### Fixed
### DMARC Manual Upload Button Not Showing
- Fixed issue where manual upload button was hidden even when enabled in settings
### DKIM Record Validation False Negatives
- Fixed DKIM validation incorrectly reporting mismatch when parameter order differs
- Changed validation from string comparison to parameter-based comparison
- DKIM records now validated correctly regardless of parameter order (e.g., `p=...;s=t` equals `s=t;p=...`)
- Follows RFC standard where DKIM parameter order is insignificant
---
## [2.0.0] - 2026-01-14
### Added

View File

@@ -1 +1 @@
2.0.3
2.0.4

View File

@@ -554,6 +554,17 @@ def parse_dkim_parameters(dkim_record: str) -> Dict[str, Any]:
}
def normalize_dkim_record(record: str) -> Dict[str, str]:
"""Parse DKIM record into normalized parameter dictionary"""
params = {}
for part in record.split(';'):
part = part.strip().replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
if '=' in part:
key, value = part.split('=', 1)
params[key.strip()] = value.strip()
return params
async def check_dkim_record(domain: str) -> Dict[str, Any]:
"""
Check DKIM record for a domain
@@ -693,7 +704,13 @@ async def check_dkim_record(domain: str) -> Dict[str, Any]:
expected_clean = expected_value.replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
actual_clean = actual_record.replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
match = expected_clean == actual_clean
# Parse both records into parameters
expected_params = normalize_dkim_record(expected_value)
actual_params = normalize_dkim_record(actual_record)
# Compare parameters, not raw strings
match = expected_params == actual_params
# match = expected_clean == actual_clean
dkim_params = parse_dkim_parameters(actual_record)

View File

@@ -16,11 +16,10 @@ from sqlalchemy import desc, or_
from sqlalchemy.exc import IntegrityError
from .config import settings, set_cached_active_domains
from .database import get_db_context
from .database import get_db_context, SessionLocal
from .mailcow_api import mailcow_api
from .models import PostfixLog, RspamdLog, NetfilterLog, MessageCorrelation
from .models import PostfixLog, RspamdLog, NetfilterLog, MessageCorrelation, DMARCSync, DomainDNSCheck
from .correlation import detect_direction, parse_postfix_message
from .models import DomainDNSCheck
from .routers.domains import check_domain_dns, save_dns_check_to_db
from .services.dmarc_imap_service import sync_dmarc_reports_from_imap
from .services.dmarc_notifications import send_dmarc_error_notification
@@ -1162,7 +1161,6 @@ async def dmarc_imap_sync_job():
# Global cleanup to ensure no other job is stuck in 'running' state
try:
# Assuming you have a way to get a DB session here
from your_app.database import SessionLocal
with SessionLocal() as db:
db.query(DMARCSync).filter(DMARCSync.status == 'running').update({
"status": "failed",

View File

@@ -11,6 +11,7 @@
let authCredentials = null;
// DMARC imap
let dmarcImapStatus = null;
let dmarcConfiguration = null;
// Load saved credentials from sessionStorage
function loadAuthCredentials() {
@@ -4612,10 +4613,29 @@ let dmarcState = {
chartInstance: null
};
async function loadDmarcSettings() {
try {
const response = await authenticatedFetch('/api/settings/info');
if (!response.ok) {
dmarcConfiguration = null;
return;
}
const data = await response.json();
dmarcConfiguration = data.dmarc_configuration || {};
console.log('DMARC settings loaded:', dmarcConfiguration);
} catch (error) {
console.error('Error loading DMARC settings:', error);
dmarcConfiguration = null;
}
}
async function loadDmarc() {
console.log('Loading DMARC tab...');
dmarcState.currentView = 'domains';
dmarcState.currentDomain = null;
await loadDmarcSettings();
await loadDmarcImapStatus();
await loadDmarcDomains();
}
@@ -5386,9 +5406,9 @@ function updateDmarcControls() {
const syncContainer = document.getElementById('dmarc-sync-container');
const lastSyncInfo = document.getElementById('dmarc-last-sync-info');
// Toggle manual upload button
// Toggle upload button
if (uploadBtn) {
if (dmarcImapStatus && dmarcImapStatus.manual_upload_enabled === true) {
if (dmarcConfiguration?.manual_upload_enabled === true) {
uploadBtn.classList.remove('hidden');
} else {
uploadBtn.classList.add('hidden');

View File

@@ -750,7 +750,7 @@
<p id="dmarc-subtitle" class="text-gray-600 dark:text-gray-400 mt-1">Monitor Domain Authentication and Global Delivery Reports</p>
</div>
</div>
<div id="dmarc-header-actions" class="flex flex-col sm:flex-row items-center gap-3 justify-center lg:justify-end">
<div id="dmarc-header-actions" class="flex flex-col sm:flex-row items-start gap-3 justify-center lg:justify-end">
<label id="dmarc-upload-btn" class="hidden cursor-pointer inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition shadow-sm">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>