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>
171 lines
8.2 KiB
HTML
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 %}
|