Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
170
templates/oauth/authorize.html
Normal file
170
templates/oauth/authorize.html
Normal file
@@ -0,0 +1,170 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user