From c3b2eb4a50d1b6d5519d3c5dacc4e30dac2dbe21 Mon Sep 17 00:00:00 2001 From: lendry Date: Fri, 26 Jun 2026 09:36:25 +0300 Subject: [PATCH] add change redirect uri from oauth --- apps/frontend/app/admin/oauth/page.tsx | 24 ++++++++ .../id/oauth-client-detail-dialog.tsx | 58 ++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/apps/frontend/app/admin/oauth/page.tsx b/apps/frontend/app/admin/oauth/page.tsx index 0d4da22..9d5390c 100644 --- a/apps/frontend/app/admin/oauth/page.tsx +++ b/apps/frontend/app/admin/oauth/page.tsx @@ -107,6 +107,29 @@ export default function AdminOAuthPage() { } } + async function handleSaveRedirectUris(client: OAuthClient, redirectUris: string[]) { + if (!token) return; + if (!redirectUris.length) { + showToast('Укажите хотя бы один redirect URI'); + return; + } + try { + const updated = await apiFetch(`/admin/rbac/oauth-clients/${client.clientId}`, { + method: 'PATCH', + body: JSON.stringify({ redirectUris }) + }, token); + showToast('Redirect URI обновлены'); + setSelectedClient((current) => + current?.clientId === client.clientId + ? { ...current, redirectUris: updated.redirectUris ?? redirectUris } + : current + ); + await loadData(); + } catch (error) { + showToast(error instanceof Error ? error.message : 'Не удалось сохранить redirect URI'); + } + } + async function handleRotateSecret(clientId: string) { if (!token) return; try { @@ -227,6 +250,7 @@ export default function AdminOAuthPage() { onRotateSecret={(clientId) => void handleRotateSecret(clientId)} onToggleActive={(client) => void handleToggleActive(client)} onDelete={(client) => void handleDeleteClient(client)} + onSaveRedirectUris={handleSaveRedirectUris} /> diff --git a/apps/frontend/components/id/oauth-client-detail-dialog.tsx b/apps/frontend/components/id/oauth-client-detail-dialog.tsx index 78d4954..9e5b8c3 100644 --- a/apps/frontend/components/id/oauth-client-detail-dialog.tsx +++ b/apps/frontend/components/id/oauth-client-detail-dialog.tsx @@ -1,6 +1,7 @@ 'use client'; -import { Copy, ExternalLink, KeyRound, RefreshCw, Trash2 } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { Copy, ExternalLink, KeyRound, Loader2, RefreshCw, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { OAuthClient } from '@/lib/api'; @@ -28,7 +29,8 @@ export function OAuthClientDetailDialog({ onCopy, onRotateSecret, onToggleActive, - onDelete + onDelete, + onSaveRedirectUris }: { client: OAuthClient | null; endpoints: OAuthEndpoints; @@ -38,7 +40,16 @@ export function OAuthClientDetailDialog({ onRotateSecret: (clientId: string) => void; onToggleActive: (client: OAuthClient) => void; onDelete: (client: OAuthClient) => void; + onSaveRedirectUris: (client: OAuthClient, redirectUris: string[]) => Promise; }) { + const [redirectUrisDraft, setRedirectUrisDraft] = useState(''); + const [savingRedirectUris, setSavingRedirectUris] = useState(false); + + useEffect(() => { + if (!client) return; + setRedirectUrisDraft(client.redirectUris.join('\n')); + }, [client]); + if (!client) return null; const primaryRedirect = client.redirectUris[0] ?? 'https://app.example.com/oauth/callback'; @@ -77,7 +88,48 @@ export function OAuthClientDetailDialog({ Client Secret выдаётся один раз при создании или после «Новый secret». Храните только на backend.

) : null} - +
+
+ Redirect URI + {client.canManage ? null : ( + + )} +
+ {client.canManage ? ( + <> +