Release version 2.0.4
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user