Skip to content

Commit 3c1614c

Browse files
committed
fix: airgap-safe Font Awesome and dynamic password policy
- Add Font Awesome to download-cdn-assets.sh for airgap deployments - Update admin.html to conditionally load Font Awesome based on ui_airgapped - Update login.html to conditionally load Font Awesome based on ui_airgapped - Update change-password-required.html: - Add airgap support for Tailwind CSS and Font Awesome - Use dynamic password policy settings from server config - Fix JavaScript validation to respect policy settings - Add missing ui_airgapped context variable to template Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
1 parent 24c92f7 commit 3c1614c

File tree

5 files changed

+87
-26
lines changed

5 files changed

+87
-26
lines changed

mcpgateway/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,6 +2997,7 @@ async def change_password_required_page(request: Request) -> HTMLResponse:
29972997
{
29982998
"request": request,
29992999
"root_path": root_path,
3000+
"ui_airgapped": settings.mcpgateway_ui_airgapped,
30003001
"password_min_length": getattr(settings, "password_min_length", 8),
30013002
"password_require_uppercase": getattr(settings, "password_require_uppercase", False),
30023003
"password_require_lowercase": getattr(settings, "password_require_lowercase", False),

mcpgateway/templates/admin.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,12 @@
104104
rel="stylesheet"
105105
/>
106106
{% endif %}
107+
<!-- Font Awesome -->
108+
{% if ui_airgapped %}
109+
<link rel="stylesheet" href="{{ root_path }}/static/vendor/fontawesome/css/all.min.css" />
110+
{% else %}
107111
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
112+
{% endif %}
108113
<link href="{{ root_path }}/static/admin.css" rel="stylesheet" />
109114
<link href="{{ root_path }}/static/gantt-chart.css" rel="stylesheet" />
110115
<link href="{{ root_path }}/static/flame-graph.css" rel="stylesheet" />

mcpgateway/templates/change-password-required.html

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>Password Change Required - MCP Gateway</title>
7+
<!-- Tailwind CSS -->
8+
{% if ui_airgapped %}
9+
<script src="{{ root_path }}/static/vendor/tailwindcss/tailwind.min.js"></script>
10+
{% else %}
711
<script src="https://cdn.tailwindcss.com"></script>
12+
{% endif %}
813
<script>
914
tailwind.config = {
1015
darkMode: "class",
@@ -54,10 +59,12 @@
5459
},
5560
};
5661
</script>
57-
<link
58-
rel="stylesheet"
59-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
60-
/>
62+
<!-- Font Awesome -->
63+
{% if ui_airgapped %}
64+
<link rel="stylesheet" href="{{ root_path }}/static/vendor/fontawesome/css/all.min.css" />
65+
{% else %}
66+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
67+
{% endif %}
6168
</head>
6269
<body class="h-full bg-gray-50 dark:bg-gray-900 overflow-hidden">
6370
<!-- Main Split Layout -->
@@ -223,21 +230,33 @@ <h3 class="text-sm font-semibold text-blue-700 dark:text-blue-300 mb-2">
223230
</h3>
224231
<ul class="text-xs text-blue-600 dark:text-blue-400 space-y-1">
225232
<li id="req-length" class="flex items-center">
226-
<i class="fas fa-circle text-green-500 mr-2"></i>
227-
At least 8 characters long
233+
<i class="fas fa-circle text-gray-400 mr-2"></i>
234+
At least {{ password_min_length or 8 }} characters long
228235
</li>
236+
{% if password_require_uppercase %}
229237
<li id="req-uppercase" class="flex items-center">
230-
<i class="fas fa-circle text-green-500 mr-2"></i>
238+
<i class="fas fa-circle text-gray-400 mr-2"></i>
231239
Contains uppercase letters (A-Z)
232240
</li>
241+
{% endif %}
242+
{% if password_require_lowercase %}
233243
<li id="req-lowercase" class="flex items-center">
234-
<i class="fas fa-circle text-green-500 mr-2"></i>
244+
<i class="fas fa-circle text-gray-400 mr-2"></i>
235245
Contains lowercase letters (a-z)
236246
</li>
247+
{% endif %}
248+
{% if password_require_numbers %}
249+
<li id="req-numbers" class="flex items-center">
250+
<i class="fas fa-circle text-gray-400 mr-2"></i>
251+
Contains numbers (0-9)
252+
</li>
253+
{% endif %}
254+
{% if password_require_special %}
237255
<li id="req-special" class="flex items-center">
238-
<i class="fas fa-circle text-green-500 mr-2"></i>
239-
Contains special characters (!@#$%&*)
256+
<i class="fas fa-circle text-gray-400 mr-2"></i>
257+
Contains special characters (!@#$%^&amp;*()_+[]{}:;"'&lt;&gt;?,.)
240258
</li>
259+
{% endif %}
241260
</ul>
242261
</div>
243262

@@ -461,6 +480,15 @@ <h4 class="text-sm font-semibold text-white mb-2">
461480
}
462481
}
463482

483+
// Password policy settings from server
484+
const passwordPolicy = {
485+
minLength: {{ password_min_length | tojson }} || 8,
486+
requireUppercase: {{ password_require_uppercase | tojson }},
487+
requireLowercase: {{ password_require_lowercase | tojson }},
488+
requireNumbers: {{ password_require_numbers | tojson }},
489+
requireSpecial: {{ password_require_special | tojson }}
490+
};
491+
464492
// Validate password requirements
465493
function validatePassword() {
466494
const password = document.getElementById('new_password').value;
@@ -471,18 +499,22 @@ <h4 class="text-sm font-semibold text-white mb-2">
471499
strengthElement.textContent = strength.label;
472500
strengthElement.className = `font-medium ${strength.color}`;
473501

474-
// Update requirement indicators
475-
updateRequirement('req-length', password.length >= 8);
476-
updateRequirement('req-uppercase', /[A-Z]/.test(password));
477-
updateRequirement('req-lowercase', /[a-z]/.test(password));
478-
updateRequirement('req-special', /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password));
502+
// Update requirement indicators (only for elements that exist)
503+
updateRequirement('req-length', password.length >= passwordPolicy.minLength);
504+
if (passwordPolicy.requireUppercase) updateRequirement('req-uppercase', /[A-Z]/.test(password));
505+
if (passwordPolicy.requireLowercase) updateRequirement('req-lowercase', /[a-z]/.test(password));
506+
if (passwordPolicy.requireNumbers) updateRequirement('req-numbers', /[0-9]/.test(password));
507+
if (passwordPolicy.requireSpecial) updateRequirement('req-special', /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password));
508+
509+
// Check password match after validation
510+
validatePasswordMatch();
479511
}
480512

481513
// Get password strength
482514
function getPasswordStrength(password) {
483515
let score = 0;
484516

485-
if (password.length >= 8) score++;
517+
if (password.length >= passwordPolicy.minLength) score++;
486518
if (/[A-Z]/.test(password)) score++;
487519
if (/[a-z]/.test(password)) score++;
488520
if (/[0-9]/.test(password)) score++;
@@ -493,10 +525,12 @@ <h4 class="text-sm font-semibold text-white mb-2">
493525
return { label: 'Strong', color: 'text-green-500' };
494526
}
495527

496-
// Update requirement indicator
528+
// Update requirement indicator (only if element exists)
497529
function updateRequirement(id, met) {
498530
const element = document.getElementById(id);
531+
if (!element) return;
499532
const icon = element.querySelector('i');
533+
if (!icon) return;
500534

501535
if (met) {
502536
icon.className = 'fas fa-check-circle text-green-500 mr-2';
@@ -522,12 +556,14 @@ <h4 class="text-sm font-semibold text-white mb-2">
522556
}
523557
}
524558

525-
// Check if password is valid
559+
// Check if password is valid based on policy
526560
function isPasswordValid(password) {
527-
return password.length >= 8 &&
528-
/[A-Z]/.test(password) &&
529-
/[a-z]/.test(password) &&
530-
/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
561+
const lengthOk = password.length >= passwordPolicy.minLength;
562+
const uppercaseOk = !passwordPolicy.requireUppercase || /[A-Z]/.test(password);
563+
const lowercaseOk = !passwordPolicy.requireLowercase || /[a-z]/.test(password);
564+
const numbersOk = !passwordPolicy.requireNumbers || /[0-9]/.test(password);
565+
const specialOk = !passwordPolicy.requireSpecial || /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
566+
return lengthOk && uppercaseOk && lowercaseOk && numbersOk && specialOk;
531567
}
532568

533569
// Handle error messages from URL parameters

mcpgateway/templates/login.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@
5959
},
6060
};
6161
</script>
62-
<link
63-
rel="stylesheet"
64-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
65-
/>
62+
<!-- Font Awesome -->
63+
{% if ui_airgapped %}
64+
<link rel="stylesheet" href="{{ root_path }}/static/vendor/fontawesome/css/all.min.css" />
65+
{% else %}
66+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
67+
{% endif %}
6668
</head>
6769
<body class="h-full bg-gray-50 dark:bg-gray-900 overflow-hidden">
6870
<!-- Main Split Layout -->

scripts/download-cdn-assets.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ mkdir -p "${STATIC_DIR}/codemirror/mode/javascript"
1414
mkdir -p "${STATIC_DIR}/codemirror/theme"
1515
mkdir -p "${STATIC_DIR}/alpinejs"
1616
mkdir -p "${STATIC_DIR}/chartjs"
17+
mkdir -p "${STATIC_DIR}/fontawesome/css"
18+
mkdir -p "${STATIC_DIR}/fontawesome/webfonts"
1719

1820
echo "📦 Downloading CDN assets for airgapped deployment..."
1921

@@ -51,6 +53,21 @@ echo " ⬇️ Chart.js 4.4.1..."
5153
curl -fsSL "https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" \
5254
-o "${STATIC_DIR}/chartjs/chart.umd.min.js"
5355

56+
# Download Font Awesome (pinned to 6.4.0 for reproducibility)
57+
echo " ⬇️ Font Awesome 6.4.0..."
58+
curl -fsSL "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" \
59+
-o "${STATIC_DIR}/fontawesome/css/all.min.css"
60+
61+
# Download Font Awesome webfonts (required for the CSS to work)
62+
echo " ⬇️ Font Awesome webfonts..."
63+
for font in fa-solid-900.woff2 fa-regular-400.woff2 fa-brands-400.woff2; do
64+
curl -fsSL "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/webfonts/${font}" \
65+
-o "${STATIC_DIR}/fontawesome/webfonts/${font}"
66+
done
67+
68+
# Fix Font Awesome CSS paths for local serving (change ../webfonts to ./webfonts)
69+
sed -i 's|../webfonts|./webfonts|g' "${STATIC_DIR}/fontawesome/css/all.min.css"
70+
5471
echo "✅ All CDN assets downloaded successfully to ${STATIC_DIR}"
5572
echo ""
5673
echo "Directory structure:"

0 commit comments

Comments
 (0)