Files
mcphub/core/templates/oauth/authorize.html
airano 7daf604a32 fix(packaging): move templates into core/ for PyPI distribution
Templates were at project root (templates/) which is outside the Python
package, so `pip install mcphub-server` would not include them. Moved to
core/templates/ and updated path references in server.py and
core/dashboard/routes.py. Also added py-modules for server.py and
server_multi.py to pyproject.toml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 02:41:28 +03:30

171 lines
8.2 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ t.page_title }}{% endblock %}
{% block content %}
<div class="auth-container px-4">
<div class="auth-card w-full max-w-md bg-white dark:bg-gray-800 rounded-lg shadow-xl p-8">
<!-- Header with Icon -->
<div class="text-center mb-6">
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-purple-100 dark:bg-purple-900 mb-4">
<svg class="h-10 w-10 text-purple-600 dark:text-purple-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
</div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ t.auth_required }}</h1>
<p class="text-gray-600 dark:text-gray-400 mt-2" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<span class="font-semibold text-purple-600 dark:text-purple-400">{{ client_name }}</span>
{{ t.wants_access.replace('{client_name}', '') }}
</p>
</div>
<!-- Client Information -->
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-6">
<div class="space-y-2 text-sm" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">{{ t.client_id_label }}</span>
<span class="font-mono text-gray-900 dark:text-white text-xs">{{ client_id }}</span>
</div>
{% if redirect_uri %}
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">{{ t.redirect_uri_label }}</span>
<span class="font-mono text-xs text-gray-900 dark:text-white truncate ml-2" title="{{ redirect_uri }}">
{{ redirect_uri[:40] }}...
</span>
</div>
{% endif %}
</div>
</div>
<!-- Requested Permissions -->
{% if scopes %}
<div class="mb-6" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">{{ t.requested_permissions }}</h3>
<div class="space-y-2">
{% for scope in scopes %}
<div class="permission-badge flex items-center p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<svg class="h-5 w-5 text-purple-600 dark:text-purple-400 {% if lang == 'fa' %}ml-3{% else %}mr-3{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ scope }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Authorization Form -->
<form method="POST" action="/oauth/authorize/confirm" id="authForm" class="space-y-4">
<!-- Hidden fields -->
<input type="hidden" name="client_id" value="{{ client_id }}">
<input type="hidden" name="redirect_uri" value="{{ redirect_uri }}">
<input type="hidden" name="response_type" value="{{ response_type }}">
<input type="hidden" name="code_challenge" value="{{ code_challenge }}">
<input type="hidden" name="code_challenge_method" value="{{ code_challenge_method }}">
{% if scope %}
<input type="hidden" name="scope" value="{{ scope }}">
{% endif %}
{% if state %}
<input type="hidden" name="state" value="{{ state }}">
{% endif %}
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<!-- API Key Input -->
<div {% if lang == 'fa' %}dir="rtl"{% endif %}>
<label for="api_key" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ t.enter_api_key }}
</label>
<input
type="password"
id="api_key"
name="api_key"
required
placeholder="{{ t.api_key_placeholder }}"
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:text-white transition"
autocomplete="off"
{% if lang == 'fa' %}dir="ltr" style="text-align: right;"{% endif %}
/>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2" {% if lang == 'fa' %}dir="rtl"{% endif %}>
{{ t.api_key_note }}
</p>
</div>
<!-- Action Buttons -->
<div class="flex gap-3 pt-4" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<button
type="submit"
name="action"
value="approve"
class="btn-primary flex-1 px-6 py-3 text-white font-semibold rounded-lg"
>
{{ t.approve }}
</button>
<button
type="submit"
name="action"
value="deny"
class="btn-secondary flex-1 px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-lg"
>
{{ t.deny }}
</button>
</div>
</form>
<!-- Security Notice -->
<div class="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg" {% if lang == 'fa' %}dir="rtl"{% endif %}>
<div class="flex {% if lang == 'fa' %}flex-row-reverse{% endif %}">
<svg class="h-5 w-5 text-blue-600 dark:text-blue-400 {% if lang == 'fa' %}mr-2{% else %}mr-2{% endif %} flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-xs text-blue-800 dark:text-blue-300">
<strong>{{ t.security_note }}</strong> {{ t.security_message }}
</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Client-side validation
document.getElementById('authForm').addEventListener('submit', function(e) {
const apiKey = document.getElementById('api_key').value;
const action = e.submitter.value;
if (action === 'approve') {
if (!apiKey || apiKey.length < 10) {
e.preventDefault();
const lang = '{{ lang }}';
const message = lang === 'fa'
? 'لطفاً یک API Key معتبر وارد کنید'
: 'Please enter a valid API key';
alert(message);
return false;
}
if (!apiKey.startsWith('cmp_') && !apiKey.startsWith('ckm_')) {
const lang = '{{ lang }}';
const message = lang === 'fa'
? 'فرمت API Key غیرمعمول به نظر می‌رسد. ادامه می‌دهید؟'
: 'The API key format looks unusual. Continue anyway?';
const confirm = window.confirm(message);
if (!confirm) {
e.preventDefault();
return false;
}
}
}
});
// Focus effect
const apiKeyInput = document.getElementById('api_key');
apiKeyInput.addEventListener('focus', function() {
this.parentElement.classList.add('ring-2', 'ring-purple-200');
});
apiKeyInput.addEventListener('blur', function() {
this.parentElement.classList.remove('ring-2', 'ring-purple-200');
});
</script>
{% endblock %}