'use client'; import { useCallback, useEffect, useState } from 'react'; import { Copy, KeyRound, Loader2, LockKeyhole, Plus, RefreshCw } from 'lucide-react'; import { AdminShell } from '@/components/id/admin-shell'; import { useAuth } from '@/components/id/auth-provider'; import { useToast } from '@/components/id/toast-provider'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { OAuthClient, OAuthScope, apiFetch } from '@/lib/api'; export default function AdminOAuthPage() { const { token } = useAuth(); const { showToast } = useToast(); const [clients, setClients] = useState([]); const [scopes, setScopes] = useState([]); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [secretDialog, setSecretDialog] = useState<{ clientId: string; clientSecret: string } | null>(null); const [form, setForm] = useState({ name: '', redirectUris: '', type: 'CONFIDENTIAL', selectedScopes: ['openid', 'profile', 'email'] as string[] }); const loadData = useCallback(async () => { if (!token) return; setLoading(true); try { const [clientsResponse, scopesResponse] = await Promise.all([ apiFetch<{ clients: OAuthClient[] }>('/admin/rbac/oauth-clients', {}, token), apiFetch<{ scopes: OAuthScope[] }>('/admin/rbac/oauth-scopes', {}, token) ]); setClients(clientsResponse.clients ?? []); setScopes(scopesResponse.scopes ?? []); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось загрузить OAuth-приложения'); } finally { setLoading(false); } }, [showToast, token]); useEffect(() => { void loadData(); }, [loadData]); async function handleCreate() { if (!token) return; const redirectUris = form.redirectUris .split('\n') .map((item) => item.trim()) .filter(Boolean); if (!form.name.trim() || !redirectUris.length || !form.selectedScopes.length) { showToast('Заполните название, redirect URI и scopes'); return; } setCreating(true); try { const created = await apiFetch('/admin/rbac/oauth-clients', { method: 'POST', body: JSON.stringify({ name: form.name.trim(), redirectUris, scopes: form.selectedScopes, type: form.type }) }, token); showToast('OAuth-приложение создано'); setDialogOpen(false); setForm({ name: '', redirectUris: '', type: 'CONFIDENTIAL', selectedScopes: ['openid', 'profile', 'email'] }); if (created.clientSecret) { setSecretDialog({ clientId: created.clientId, clientSecret: created.clientSecret }); } await loadData(); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось создать приложение'); } finally { setCreating(false); } } async function handleRotateSecret(clientId: string) { if (!token) return; try { const response = await apiFetch<{ clientId: string; clientSecret: string }>(`/admin/rbac/oauth-clients/${clientId}/rotate-secret`, { method: 'POST' }, token); setSecretDialog(response); showToast('Новый client secret сгенерирован'); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось перевыпустить secret'); } } async function handleToggleActive(client: OAuthClient) { if (!token) return; try { await apiFetch(`/admin/rbac/oauth-clients/${client.clientId}`, { method: 'PATCH', body: JSON.stringify({ isActive: !client.isActive }) }, token); showToast(client.isActive ? 'Приложение отключено' : 'Приложение включено'); await loadData(); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось обновить приложение'); } } function copyText(value: string, label: string) { void navigator.clipboard.writeText(value); showToast(`${label} скопирован`); } return (

OAuth-приложения

Создание клиентов как на oauth.yandex.ru: client_id, secret, redirect URI и scopes

{loading ? (
Загрузка...
) : (
{clients.map((client) => ( {client.name} {client.type === 'PUBLIC' ? 'Публичное приложение' : 'Confidential приложение'}
Client ID
{client.clientId}
Redirect URI
{client.redirectUris.map((uri) => (
{uri}
))}
Scopes

{client.scopes.join(', ')}

{client.type !== 'PUBLIC' ? ( ) : null}
))} {!clients.length ?

OAuth-приложения ещё не созданы

: null}
)} Новое OAuth-приложение
setForm((current) => ({ ...current, name: event.target.value }))} placeholder="Lendry Docs" />